import React, { useState, useCallback, useEffect, useRef } from 'react';
import type { Submission, GeIdResult, ResultDetail, FileSystemItem, DownloadHistoryEntry, DownloadJob, DownloadedFile, FileDownload, DownloadBatch, ProjectDetails } from './types';
import SubmissionForm from './components/SubmissionForm';
import SubmissionHistory from './components/SubmissionHistory';
import UserManagementModal from './components/UserManagementModal';
import CustomPathManagerModal from './components/CustomPathManagerModal';
import QueueStatus from './components/QueueStatus';
import type { GeIdItem } from './components/QueueStatus';
import QueueManagementModal from './components/QueueManagementModal';
import ErrorDetailModal from './components/ErrorDetailModal';
import Navigation from './components/Navigation';
import RawDownloadForm from './components/RawDownloadForm';
import FileList from './components/FileList';
import DownloadQueueStatus from './components/DownloadQueueStatus';
import DownloadHistory from './components/DownloadHistory';
import OtpModal from './components/OtpModal';
import ProjectInfoModal from './components/ProjectInfoModal';
import NotificationManager, { addMySubmission } from './components/NotificationManager';
import CheckPage from './components/CheckPage';
import { useRealtimeDownloads, DownloadRecord } from './utils/use-realtime-downloads';
import { useRealtimeSubmissions, SubmissionRecord } from './utils/use-realtime-submissions';
import { useTabVisibility } from './hooks';

// Helper function to format file size
const formatFileSize = (bytes: number | undefined): string | undefined => {
  if (!bytes || bytes === 0) return undefined;

  const units = ['B', 'KB', 'MB', 'GB', 'TB'];
  let size = bytes;
  let unitIndex = 0;

  while (size >= 1024 && unitIndex < units.length - 1) {
    size /= 1024;
    unitIndex++;
  }

  // Format with 2 decimal places for KB and above, no decimals for bytes
  const formatted = unitIndex === 0
    ? size.toString()
    : size.toFixed(2);

  return `${formatted} ${units[unitIndex]}`;
};

// --- MOCK DATA ---
// --- APP COMPONENT ---
type Page = 'permission' | 'rawDownload' | 'check';

const App: React.FC = () => {
  // Track tab visibility để pause polling khi tab ẩn
  const isTabVisibleRef = useTabVisibility();

  // Navigation debounce timer ref
  const navigationTimerRef = React.useRef<NodeJS.Timeout | null>(null);

  // Operation lock to prevent race conditions between actions
  const operationLockRef = React.useRef<boolean>(false);
  const operationQueueRef = React.useRef<(() => Promise<void>)[]>([]);

  // Load current page from localStorage, default to 'permission'
  const [currentPage, setCurrentPage] = useState<Page>(() => {
    // Check URL params first
    if (typeof window !== 'undefined') {
      const params = new URLSearchParams(window.location.search);
      const pageParam = params.get('page');
      if (pageParam === 'permission' || pageParam === 'rawDownload' || pageParam === 'check') {
        return pageParam;
      }
    }

    const saved = localStorage.getItem('currentPage');
    return (saved === 'permission' || saved === 'rawDownload' || saved === 'check') ? saved : 'permission';
  });

  // Save current page to localStorage whenever it changes
  React.useEffect(() => {
    localStorage.setItem('currentPage', currentPage);

    // Update URL without reloading
    const url = new URL(window.location.href);
    url.searchParams.set('page', currentPage);
    window.history.pushState({}, '', url);
  }, [currentPage]);

  // Cleanup navigation timer on unmount
  React.useEffect(() => {
    return () => {
      if (navigationTimerRef.current) {
        clearTimeout(navigationTimerRef.current);
      }
    };
  }, []);

  // Helper: Execute operation with lock to prevent race conditions
  // Operations are queued and executed sequentially
  const executeWithLock = React.useCallback(async (operation: () => Promise<void>) => {
    // Add to queue
    operationQueueRef.current.push(operation);

    // If already processing, let the queue handler process it
    if (operationLockRef.current) return;

    // Process queue
    operationLockRef.current = true;
    try {
      while (operationQueueRef.current.length > 0) {
        const nextOperation = operationQueueRef.current.shift();
        if (nextOperation) {
          await nextOperation();
          // Small delay between operations to prevent UI jank
          await new Promise(resolve => setTimeout(resolve, 200));
        }
      }
    } finally {
      operationLockRef.current = false;
    }
  }, []);

  // State for Permission Page
  const [username, setUsername] = useState<string>('');
  const [geIdAndLang, setGeIdAndLang] = useState<string>('');
  const [submissions, setSubmissions] = useState<Submission[]>([]);
  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);
  const [currentSubmission, setCurrentSubmission] = useState<{ username: string; geIdAndLang: string } | null>(null);
  const [isUserModalOpen, setIsUserModalOpen] = useState<boolean>(false);
  const [isCustomPathModalOpen, setIsCustomPathModalOpen] = useState<boolean>(false);
  const [isQueueModalOpen, setIsQueueModalOpen] = useState<boolean>(false);
  const [queueItems, setQueueItems] = useState<GeIdItem[]>([]);
  const [pendingSubmissionsCount, setPendingSubmissionsCount] = useState<number>(0);
  const [isErrorDetailModalOpen, setIsErrorDetailModalOpen] = useState(false);
  const [errorDetailContent, setErrorDetailContent] = useState<string | null>(null);
  const [isProjectInfoModalOpen, setIsProjectInfoModalOpen] = useState(false);
  const [projectInfoData, setProjectInfoData] = useState<ProjectDetails | null>(null);
  const [isLoadingProjectInfo, setIsLoadingProjectInfo] = useState(false);

  // State for Raw Download Page
  const [rawGeId, setRawGeId] = useState('');
  const [isSearching, setIsSearching] = useState(false);
  const [isRawDownloading, setIsRawDownloading] = useState(false);
  const [fileList, setFileList] = useState<FileSystemItem[]>([]);
  const [rawError, setRawError] = useState<string | null>(null);
  const [relatedProjects, setRelatedProjects] = useState<Array<{ ge_id: string, lang: string }>>([]);
  const [projectNote, setProjectNote] = useState<string | null>(null);
  const [currentGeId, setCurrentGeId] = useState<string>('');
  const [currentLang, setCurrentLang] = useState<string>('');
  const [autoSearchQuery, setAutoSearchQuery] = useState<string>(''); // Auto search from 3rd param

  // Track active polling intervals for cleanup
  const sharingPollIntervalRef = React.useRef<NodeJS.Timeout | null>(null);

  // NEW: Unified state for all file downloads
  const [allDownloads, setAllDownloads] = useState<FileDownload[]>([]);

  // Computed values - group files by batch (memoized to prevent unnecessary re-renders)
  const groupFilesByBatch = React.useCallback((downloads: FileDownload[]): Map<string, FileDownload[]> => {
    const groups = new Map<string, FileDownload[]>();
    downloads.forEach(file => {
      const batchId = file.batch_id || 'unknown';
      if (!groups.has(batchId)) {
        groups.set(batchId, []);
      }
      groups.get(batchId)!.push(file);
    });
    return groups;
  }, []);

  const activeDownloads = React.useMemo(() =>
    allDownloads.filter(d =>
      d.status === 'pending' || d.status === 'downloading'
    ),
    [allDownloads]
  );

  const historyDownloads = React.useMemo(() =>
    allDownloads.filter(d =>
      d.status === 'completed' || d.status === 'failed' || d.status === 'cancelled'
    ),
    [allDownloads]
  );

  // Memoize downloading count to avoid recalculation on every render
  const downloadingFilesCount = React.useMemo(() =>
    allDownloads.filter(d => d.status === 'downloading').length,
    [allDownloads]
  );

  // Error modal handlers - stable references
  const showCriticalError = useCallback((message: string) => {
    setErrorDetailContent(message);
    setIsErrorDetailModalOpen(true);
  }, []);

  const handleErrorClick = useCallback((details: string) => {
    setErrorDetailContent(details);
    setIsErrorDetailModalOpen(true);
  }, []);

  const closeErrorModal = useCallback(() => {
    setIsErrorDetailModalOpen(false);
  }, []);

  const [selectedFileIds, setSelectedFileIds] = useState<string[]>([]);
  const [isOtpModalOpen, setIsOtpModalOpen] = useState(false);
  const [isOtpLoading, setIsOtpLoading] = useState(false);
  const [otpError, setOtpError] = useState<string | null>(null);
  const [pendingSearchParams, setPendingSearchParams] = useState<{ geId: string, lang: string } | null>(null);
  const [otpCallback, setOtpCallback] = useState<((otpCode: string) => Promise<void>) | null>(null);
  const [isDownloadButtonLoading, setIsDownloadButtonLoading] = useState(false);
  const [currentSharingId, setCurrentSharingId] = useState<string>(''); // Track if viewing sharing link
  const [currentSharingContext, setCurrentSharingContext] = useState<{ geId: string, lang: string } | null>(null); // Track GE ID + Lang for sharing link
  const [currentMode, setCurrentMode] = useState<'api' | 'sharing' | null>(null); // Track current download mode

  // Navigation history for folder browsing
  const [navigationHistory, setNavigationHistory] = useState<string[]>([]);
  const [navigationIndex, setNavigationIndex] = useState<number>(-1);
  const [currentPath, setCurrentPath] = useState<string>('');
  const [customPath, setCustomPath] = useState<string | null>(null);
  const [hasCustomPath, setHasCustomPath] = useState<boolean>(false);
  const [isSavingCustomPath, setIsSavingCustomPath] = useState<boolean>(false);

  // Setup global function for opening custom path manager modal
  React.useEffect(() => {
    (window as any).openCustomPathManager = () => {
      setIsCustomPathModalOpen(true);
    };
    return () => {
      delete (window as any).openCustomPathManager;
    };
  }, []);

  // Helper: Convert Supabase record to FileDownload format
  const convertToFileDownload = useCallback((record: DownloadRecord): FileDownload => ({
    id: record.id,
    batch_id: record.batch_id,
    ge_id: record.ge_id,
    lang: record.lang,
    file_name: record.file_name,
    file_path: record.file_path,
    mode: record.mode,
    status: record.status,
    destination_path: record.destination_path || undefined,
    file_size: record.file_size || undefined,
    downloaded_size: record.downloaded_size || 0,
    progress_percent: record.progress_percent || 0,
    created_at: record.created_at || '',
    started_at: record.started_at || undefined,
    completed_at: record.completed_at || undefined,
    error_message: record.error_message || undefined,
    retry_count: record.retry_count || 0,
    sharing_id: record.sharing_id || undefined,
    mongodb_path: record.mongodb_path || undefined,
  }), []);

  // Realtime handlers for downloads
  const handleDownloadInsert = useCallback((record: DownloadRecord) => {
    const newDownload = convertToFileDownload(record);
    setAllDownloads(prev => {
      // Check if already exists (avoid duplicates)
      if (prev.some(d => d.id === newDownload.id)) return prev;
      return [newDownload, ...prev];
    });
  }, [convertToFileDownload]);

  const handleDownloadUpdate = useCallback((record: DownloadRecord) => {
    const updated = convertToFileDownload(record);
    setAllDownloads(prev => prev.map(d => {
      if (d.id !== updated.id) return d;

      // Smart merge: prevent progress regression
      const merged = { ...updated };

      // Rule 1: Never downgrade progress
      if (d.progress_percent && updated.progress_percent) {
        merged.progress_percent = Math.max(d.progress_percent, updated.progress_percent);
      }

      // Rule 2: Never reduce downloaded_size
      if (d.downloaded_size && updated.downloaded_size) {
        merged.downloaded_size = Math.max(d.downloaded_size, updated.downloaded_size);
      }

      // Rule 3: Status priority (don't revert completed → downloading)
      const statusPriority: Record<string, number> = {
        'completed': 4, 'failed': 3, 'downloading': 2, 'pending': 1, 'cancelled': 0
      };
      if ((statusPriority[d.status] || 0) > (statusPriority[updated.status] || 0)) {
        merged.status = d.status;
      }

      return merged;
    }));
  }, [convertToFileDownload]);

  const handleDownloadDelete = useCallback((record: DownloadRecord) => {
    setAllDownloads(prev => prev.filter(d => d.id !== record.id));
  }, []);

  // Subscribe to realtime downloads (only when on rawDownload page)
  useRealtimeDownloads(currentPage === 'rawDownload' ? {
    onInsert: handleDownloadInsert,
    onUpdate: handleDownloadUpdate,
    onDelete: handleDownloadDelete,
  } : {});

  // Realtime handlers for TMS submissions
  const handleSubmissionUpdate = useCallback((record: SubmissionRecord) => {
    // Only handle completed/failed submissions for history
    if (record.status === 'completed' || record.status === 'failed') {
      const results = record.results
        ? (typeof record.results === 'string' ? JSON.parse(record.results) : record.results)
        : [];

      const newSubmission: Submission = {
        id: record.submission_id,
        username: (record.input?.username_list || []).join('\n'),
        geIdAndLang: record.input?.ge_input || '',
        timestamp: new Date(record.created_at),
        results: formatResults(results, record.input?.ge_input || '')
      };

      setSubmissions(prev => {
        // Check if already exists
        if (prev.some(s => s.id === newSubmission.id)) {
          return prev.map(s => s.id === newSubmission.id ? newSubmission : s);
        }
        return [newSubmission, ...prev].sort((a, b) =>
          b.timestamp.getTime() - a.timestamp.getTime()
        );
      });

      // Clear queue when submission completes
      setQueueItems([]);
      setCurrentSubmission(null);
      setIsLoading(false);
    }
  }, []);

  const handleSubmissionDelete = useCallback((record: SubmissionRecord) => {
    setSubmissions(prev => prev.filter(s => s.id !== record.submission_id));
  }, []);

  // Subscribe to realtime submissions (only when on permission page)
  useRealtimeSubmissions(currentPage === 'permission' ? {
    onUpdate: handleSubmissionUpdate,
    onDelete: handleSubmissionDelete,
    submissionType: 'tms_permission',
  } : {});

  // Initial fetch for downloads when entering rawDownload page
  React.useEffect(() => {
    let isMounted = true;

    const loadAllDownloads = async () => {
      if (!isMounted) return;

      try {
        const response = await fetch('/api/downloads?limit=500');
        const data = await response.json();

        if (isMounted && data.success && data.downloads) {
          setAllDownloads(data.downloads as FileDownload[]);
        }
      } catch (error) {
        console.error('Failed to load downloads:', error);
      }
    };

    // Only load when on rawDownload page
    if (currentPage === 'rawDownload') {
      loadAllDownloads();
    }

    return () => {
      isMounted = false;
      // Cleanup sharing poll interval
      if (sharingPollIntervalRef.current) {
        clearInterval(sharingPollIntervalRef.current);
        sharingPollIntervalRef.current = null;
      }
    };
  }, [currentPage]);

  // Load Permission page data
  React.useEffect(() => {
    if (currentPage === 'permission') {
      loadSubmissionsHistory();
    }
  }, [currentPage]);

  // Load submissions history from Supabase
  const loadSubmissionsHistory = async () => {
    try {
      console.log('Loading submissions history...');
      const response = await fetch('/api/submissions?limit=30');
      const data = await response.json();
      console.log('Submissions history response:', data);

      if (data.success && data.data) {
        // Convert Supabase format to Submission format
        const history: Submission[] = data.data
          .filter((s: any) => s.status === 'completed' || s.status === 'failed')
          .map((s: any) => ({
            id: s.submission_id,
            username: (s.input?.username_list || []).join('\n'),
            geIdAndLang: s.input?.ge_input || '',
            timestamp: new Date(s.created_at),
            results: s.results ? formatResults(
              typeof s.results === 'string' ? JSON.parse(s.results) : s.results,
              s.input?.ge_input || ''
            ) : []
          }))
          .sort((a: Submission, b: Submission) =>
            b.timestamp.getTime() - a.timestamp.getTime()
          );

        setSubmissions(history);
      }
    } catch (error) {
      console.error('Failed to load submissions history:', error);
    }
  };

  const handlePermissionSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!username.trim() || !geIdAndLang.trim()) {
      setError('Cả hai trường Username và GE ID & Lang đều là bắt buộc.');
      return;
    }

    setError(null);
    setIsLoading(true);
    setCurrentSubmission({ username, geIdAndLang });

    const geIdLines = geIdAndLang.trim().split('\n').filter(Boolean);
    const usernames = username.trim().split('\n').filter(Boolean);

    // Create initial queue items for UI
    const initialQueueItems: GeIdItem[] = geIdLines.map((line, index) => {
      const [id = '', lang = ''] = line.split(/\s+/);
      return { key: `${Date.now()}-${index}`, id, lang, status: 'waiting', usernames: username };
    });
    setQueueItems(initialQueueItems);

    try {
      // Create submission in backend (src/ backend via Vite proxy)
      const response = await fetch('/api/submit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          submission_type: 'tms_permission',
          username_list: usernames,
          ge_input: geIdAndLang
        })
      });

      const data = await response.json();

      if (!response.ok) {
        throw new Error(data.error || 'Không thể tạo submission');
      }

      console.log('[handlePermissionSubmit] Created submission:', data);

      // Start polling for status (submission_id returned from backend)
      const submissionId = data.data?.submission_id;
      if (submissionId) {
        // Save to localStorage for notification filtering
        addMySubmission(submissionId);
        startPollingSubmission(submissionId);
      }

      // Giữ nguyên nội dung input (không xoá form)

    } catch (error: any) {
      console.error('[handlePermissionSubmit] Error:', error);
      setError(error.message || 'Có lỗi xảy ra khi gửi yêu cầu');
      setIsLoading(false);
      setCurrentSubmission(null);
      setQueueItems([]);
    }
  };

  // Poll submission status
  const startPollingSubmission = (submissionId: string) => {
    const pollInterval = setInterval(async () => {
      // Skip polling if tab is hidden
      if (!isTabVisibleRef.current) return;

      try {
        const response = await fetch(`/api/submissions/${submissionId}`);
        const data = await response.json();

        if (!data.success) {
          clearInterval(pollInterval);
          return;
        }

        const submission = data.data;
        console.log('[startPollingSubmission] Status:', submission.status);

        // Update queue items based on status
        if (submission.status === 'processing') {
          // Mark first waiting item as processing
          setQueueItems(prev => {
            const waitingIndex = prev.findIndex(item => item.status === 'waiting');
            if (waitingIndex !== -1) {
              return prev.map((item, idx) =>
                idx === waitingIndex ? { ...item, status: 'processing' } : item
              );
            }
            return prev;
          });
        } else if (submission.status === 'completed' || submission.status === 'failed') {
          clearInterval(pollInterval);

          // Parse results and update queue
          if (submission.results) {
            const results = typeof submission.results === 'string'
              ? JSON.parse(submission.results)
              : submission.results;

            // Update queue items to done/error
            setQueueItems(prev => prev.map(item => ({
              ...item,
              status: 'done' // Simplified for now
            })));

            // Add to submissions history
            const newSubmission: Submission = {
              id: submissionId,
              username: submission.usernames.join('\n'),
              geIdAndLang: submission.ge_input,
              timestamp: new Date(submission.created_at),
              results: formatResults(results, submission.ge_input)
            };

            setSubmissions(prev => [newSubmission, ...prev].sort((a, b) =>
              b.timestamp.getTime() - a.timestamp.getTime()
            ));
          }

          setIsLoading(false);
          setCurrentSubmission(null);
          setQueueItems([]);
        }
      } catch (error) {
        console.error('[startPollingSubmission] Polling error:', error);
      }
    }, 2000); // Poll every 2 seconds
  };

  // Format backend results to match frontend structure
  const formatResults = (backendResults: any[], geInput: string): GeIdResult[] => {
    if (!backendResults || !Array.isArray(backendResults)) {
      return [];
    }

    // Group results by ge_id + lang
    const groupedResults = new Map<string, any[]>();

    for (const result of backendResults) {
      // Handle both old format [username, message] and new format {ge_id, lang, status, message, ...}
      if (Array.isArray(result)) {
        // Old format: [username, message] - skip or handle gracefully
        continue;
      }

      const key = `${result.ge_id} ${result.lang}`;
      if (!groupedResults.has(key)) {
        groupedResults.set(key, []);
      }
      groupedResults.get(key)!.push(result);
    }

    // Convert to GeIdResult format
    const geIdResults: GeIdResult[] = [];

    for (const [geIdLang, results] of groupedResults) {
      const details: ResultDetail[] = results.map((r: any) => ({
        username: r.username || 'Unknown',
        url: r.tms_url || '',
        status: r.status === 'success' ? 'success' : 'error',
        message: r.message || '',
        ...(r.status === 'error' && { errorDetails: r.message })
      }));

      geIdResults.push({
        geIdAndLang: geIdLang,
        completionTime: new Date(results[0]?.processed_at || new Date()),
        details
      });
    }

    return geIdResults;
  };

  const handleRawDownload = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!rawGeId.trim()) {
      return; // Silent validation
    }

    const lines = rawGeId.trim().split('\n');
    if (lines.length === 0) {
      return; // Silent validation
    }

    const firstLine = lines[0].trim();
    const parts = firstLine.split(/\s+/);
    const [geId, lang, searchQuery] = parts; // Extract search query (3rd param)

    if (!geId || !lang) {
      return; // Silent validation
    }

    setIsRawDownloading(true);
    setFileList([]);
    setSelectedFileIds([]);
    setCurrentSharingId('');
    setCurrentMode('sharing'); // Set mode to Sharing
    setCurrentGeId(geId);
    setCurrentLang(lang.toUpperCase());
    setAutoSearchQuery(searchQuery || ''); // Set auto search query from 3rd param

    // Reset navigation history
    setNavigationHistory([]);
    setNavigationIndex(-1);
    setCurrentPath('');

    try {
      // Query MongoDB for linkRaw
      const dbResponse = await fetch('/api/sharing-link/get-from-db', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ge_id: geId, lang: lang })
      });

      if (!dbResponse.ok) {
        const errorData = await dbResponse.json();

        // Format error message with record details
        let errorMessage = '';
        if (errorData.detail?.error) {
          errorMessage = `${errorData.detail.error}\n\n`;

          if (errorData.detail.record) {
            const rec = errorData.detail.record;
            errorMessage += `Record:\n`;
            errorMessage += `- GE ID: ${rec.geId}\n`;
            errorMessage += `- Lang: ${rec.lang}\n`;
            errorMessage += `- linkRaw: ${rec.linkRaw || '(trống)'}\n`;
            errorMessage += `- path: ${rec.path || '(trống)'}`;
          } else if (errorData.detail.records) {
            errorMessage += `Tìm thấy ${errorData.detail.records.length} records:\n\n`;
            errorData.detail.records.forEach((rec: any, idx: number) => {
              errorMessage += `Record ${idx + 1}:\n`;
              errorMessage += `- GE ID: ${rec.geId}\n`;
              errorMessage += `- Lang: ${rec.lang}\n`;
              errorMessage += `- linkRaw: ${rec.linkRaw || '(trống)'}\n`;
              errorMessage += `- path: ${rec.path || '(trống)'}\n\n`;
            });
          } else if (errorData.detail.query) {
            errorMessage += `Query không tìm thấy:\n`;
            errorMessage += `- GE ID: ${errorData.detail.query.geId}\n`;
            errorMessage += `- Lang: ${errorData.detail.query.lang}`;
          }
        } else {
          errorMessage = errorData.detail || 'Lỗi không xác định';
        }

        showCriticalError(errorMessage);
        setIsRawDownloading(false);
        return;
      }

      const dbData = await dbResponse.json();
      const sharingLink = dbData.sharing_link;

      // Fetch related projects (tựa cùng raw)
      try {
        const relatedResponse = await fetch(`/api/sharing-link/related-projects?link_raw=${encodeURIComponent(sharingLink)}`);
        if (relatedResponse.ok) {
          const relatedData = await relatedResponse.json();
          if (relatedData.success && relatedData.projects) {
            setRelatedProjects(relatedData.projects);
          }
        }
      } catch (err) {
        console.error('Failed to fetch related projects:', err);
        // Non-critical error - continue anyway
      }

      // Fetch project note
      try {
        const noteResponse = await fetch(`/api/sharing-link/project-note?ge_id=${encodeURIComponent(geId)}&lang=${encodeURIComponent(lang)}`);
        if (noteResponse.ok) {
          const noteData = await noteResponse.json();
          if (noteData.success) {
            setProjectNote(noteData.note);
          }
        }
      } catch (err) {
        console.error('Failed to fetch project note:', err);
        // Non-critical error - continue anyway
      }

      // Fetch custom path for this GE ID
      let fetchedCustomPath: string | null = null;
      try {
        const customPathResponse = await fetch(`/api/custom-paths/${encodeURIComponent(geId)}`);
        if (customPathResponse.ok) {
          const customPathData = await customPathResponse.json();
          if (customPathData.success && customPathData.custom_path) {
            fetchedCustomPath = customPathData.custom_path;
            setCustomPath(customPathData.custom_path);
            setHasCustomPath(true);
          } else {
            setCustomPath(null);
            setHasCustomPath(false);
          }
        } else {
          setCustomPath(null);
          setHasCustomPath(false);
        }
      } catch (err) {
        console.error('Failed to fetch custom path:', err);
        setCustomPath(null);
        setHasCustomPath(false);
      }

      // Process sharing link
      const processResponse = await fetch('/api/sharing-link/process', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ url: sharingLink })
      });

      const submitData = await processResponse.json();

      if (!submitData.success) {
        showCriticalError(submitData.message || 'Lỗi xử lý sharing link');
        setIsRawDownloading(false);
        return;
      }

      // Poll for result
      const requestId = submitData.request_id;

      // Clear any existing polling interval
      if (sharingPollIntervalRef.current) {
        clearInterval(sharingPollIntervalRef.current);
      }

      const pollInterval = setInterval(async () => {
        // Skip polling if tab is hidden
        if (!isTabVisibleRef.current) return;

        try {
          // Check if OTP is required
          const otpStatusResponse = await fetch('/api/sharing-link/otp-status');
          const otpStatusData = await otpStatusResponse.json();

          if (otpStatusData.otp_required) {
            if (sharingPollIntervalRef.current) {
              clearInterval(sharingPollIntervalRef.current);
              sharingPollIntervalRef.current = null;
            }
            setIsRawDownloading(false);

            // Show OTP modal
            setOtpError(null);
            setIsOtpLoading(false);
            setIsOtpModalOpen(true);
            setOtpCallback(() => async (otpCode: string) => {
              try {
                const otpResponse = await fetch('/api/sharing-link/submit-otp', {
                  method: 'POST',
                  headers: { 'Content-Type': 'application/json' },
                  body: JSON.stringify({ otp_code: otpCode })
                });

                const otpData = await otpResponse.json();

                if (otpData.success) {
                  setIsRawDownloading(true);
                  handleRawDownload(e); // Retry
                } else {
                  showCriticalError(otpData.message || 'OTP không hợp lệ');
                }
              } catch (error) {
                showCriticalError('Lỗi gửi OTP');
                console.error('OTP submission error:', error);
              }
            });
            return;
          }

          // Continue checking result
          const resultResponse = await fetch(`/api/sharing-link/result/${requestId}`);
          const data = await resultResponse.json();

          if (data.status === 'success') {
            if (sharingPollIntervalRef.current) {
              clearInterval(sharingPollIntervalRef.current);
              sharingPollIntervalRef.current = null;
            }

            // Convert files to FileSystemItem format
            const items: FileSystemItem[] = (data.files || []).map((file: any) => {
              // Format modified time - handle both Unix timestamp (number) and ISO string
              let modifiedStr: string | undefined = undefined;
              const timeValue = file.additional?.time?.mtime || file.modified || file.time;
              if (timeValue) {
                if (typeof timeValue === 'number') {
                  // Unix timestamp - convert to readable date
                  modifiedStr = new Date(timeValue * 1000).toLocaleString('vi-VN', {
                    year: 'numeric',
                    month: '2-digit',
                    day: '2-digit',
                    hour: '2-digit',
                    minute: '2-digit'
                  });
                } else {
                  modifiedStr = timeValue;
                }
              }

              return {
                id: file.path || file.name,
                name: file.name,
                type: file.is_folder ? 'folder' : 'file',
                size: file.is_folder ? undefined : formatFileSize(file.additional?.size),
                modified: modifiedStr,
                path: file.path
              };
            });

            // Don't set fileList yet if we have custom path
            if (!fetchedCustomPath) {
              setFileList(items);
            }

            // Set navigation for sharing link
            const targetPath = fetchedCustomPath || data.path || '/';

            // If custom path exists, navigate to it directly
            if (fetchedCustomPath) {
              // Fetch folder list at custom path
              try {
                const customFolderResponse = await fetch('/api/sharing-link/list-folder', {
                  method: 'POST',
                  headers: { 'Content-Type': 'application/json' },
                  body: JSON.stringify({
                    sharing_id: data.sharing_id,
                    folder_path: fetchedCustomPath
                  })
                });

                if (customFolderResponse.ok) {
                  const customFolderData = await customFolderResponse.json();

                  console.log('Custom path response:', customFolderData);

                  if (customFolderData.status === 'success') {
                    const customItems: FileSystemItem[] = (customFolderData.files || []).map((file: any) => {
                      let modifiedStr: string | undefined = undefined;
                      const timeValue = file.additional?.time?.mtime || file.modified || file.time;
                      if (timeValue) {
                        if (typeof timeValue === 'number') {
                          modifiedStr = new Date(timeValue * 1000).toLocaleString('vi-VN', {
                            year: 'numeric',
                            month: '2-digit',
                            day: '2-digit',
                            hour: '2-digit',
                            minute: '2-digit'
                          });
                        } else {
                          modifiedStr = timeValue;
                        }
                      }

                      return {
                        id: file.path || file.name,
                        name: file.name,
                        type: file.is_folder ? 'folder' : 'file',
                        size: file.is_folder ? undefined : formatFileSize(file.additional?.size),
                        modified: modifiedStr,
                        path: file.path
                      };
                    });
                    setFileList(customItems);
                    console.log('Custom path files loaded:', customItems.length);
                  } else {
                    console.error('Custom path failed:', customFolderData.message);
                    // Fall back to root folder
                    setFileList(items);
                  }
                } else {
                  console.error('Custom folder response not ok:', customFolderResponse.status);
                  setFileList(items);
                }
              } catch (err) {
                console.error('Failed to navigate to custom path:', err);
                // Fall back to root folder if custom path fails
                setFileList(items);
              }
            }

            // Setup navigation history correctly
            // If custom path exists, include root path in history so Back button works
            if (fetchedCustomPath && fetchedCustomPath !== (data.path || '/')) {
              setNavigationHistory([data.path || '/', fetchedCustomPath]);
              setNavigationIndex(1); // Current is at custom path (index 1)
            } else {
              setNavigationHistory([targetPath]);
              setNavigationIndex(0);
            }

            setCurrentPath(targetPath);
            setCurrentSharingId(data.sharing_id);

            // Save GE ID + Lang context for later download
            setCurrentSharingContext({ geId, lang });

            setIsRawDownloading(false);
          } else if (data.status === 'error') {
            if (sharingPollIntervalRef.current) {
              clearInterval(sharingPollIntervalRef.current);
              sharingPollIntervalRef.current = null;
            }
            showCriticalError(data.message || 'Lỗi xử lý sharing link');
            setIsRawDownloading(false);
          }
        } catch (error) {
          if (sharingPollIntervalRef.current) {
            clearInterval(sharingPollIntervalRef.current);
            sharingPollIntervalRef.current = null;
          }
          showCriticalError('Lỗi kết nối khi xử lý sharing link');
          setIsRawDownloading(false);
        }
      }, 2000);

      // Store interval ref for cleanup
      sharingPollIntervalRef.current = pollInterval;

    } catch (error) {
      showCriticalError('Lỗi kết nối đến server');
      setIsRawDownloading(false);
    }
  };

  const handleRawSearch = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!rawGeId.trim()) {
      return; // Silent validation - no error message
    }

    const lines = rawGeId.trim().split('\n');
    if (lines.length === 0) {
      return; // Silent validation - no error message
    }

    const firstLine = lines[0].trim();

    // Parse GE ID + Lang (only for API download logic)
    const [geId, lang] = firstLine.split(/\s+/);
    if (!geId || !lang) {
      return; // Silent validation - no error message
    }

    setIsSearching(true);
    setFileList([]);
    setSelectedFileIds([]);
    setCurrentSharingId(''); // Reset sharing ID
    setCurrentSharingContext(null); // Reset sharing context
    setCurrentMode('api'); // Set mode to API
    setCurrentGeId(geId);
    setCurrentLang(lang.toUpperCase());
    setAutoSearchQuery(''); // Clear search query

    // Reset navigation history on new search
    setNavigationHistory([]);
    setNavigationIndex(-1);
    setCurrentPath('');

    try {
      // Fetch related projects (tựa cùng raw)
      try {
        const relatedResponse = await fetch(`/api/sharing-link/related-projects-by-ge?ge_id=${encodeURIComponent(geId)}&lang=${encodeURIComponent(lang)}`);
        if (relatedResponse.ok) {
          const relatedData = await relatedResponse.json();
          if (relatedData.success && relatedData.projects) {
            setRelatedProjects(relatedData.projects);
          }
        }
      } catch (err) {
        console.error('Failed to fetch related projects:', err);
        // Non-critical error - continue anyway
      }

      // Fetch project note
      try {
        const noteResponse = await fetch(`/api/sharing-link/project-note?ge_id=${encodeURIComponent(geId)}&lang=${encodeURIComponent(lang)}`);
        if (noteResponse.ok) {
          const noteData = await noteResponse.json();
          if (noteData.success) {
            setProjectNote(noteData.note);
          }
        }
      } catch (err) {
        console.error('Failed to fetch project note:', err);
        // Non-critical error - continue anyway
      }

      // Handle GE ID + Lang (API download logic - fetch from NAS via API)
      const response = await fetch('/api/raw-files/list', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ge_id: geId, lang: lang })
      });

      const data = await response.json();

      if (data.status === 'success') {
        // Convert files to FileSystemItem format
        const items: FileSystemItem[] = data.files.map((file: any) => {
          // Format modified time - handle both Unix timestamp (number) and ISO string
          let modifiedStr: string | undefined = undefined;
          const timeValue = file.additional?.time?.mtime || file.modified || file.time;
          if (timeValue) {
            if (typeof timeValue === 'number') {
              // Unix timestamp - convert to readable date
              modifiedStr = new Date(timeValue * 1000).toLocaleString('vi-VN', {
                year: 'numeric',
                month: '2-digit',
                day: '2-digit',
                hour: '2-digit',
                minute: '2-digit'
              });
            } else {
              modifiedStr = timeValue;
            }
          }

          return {
            id: file.path || file.name,
            name: file.name,
            type: file.isdir ? 'folder' : 'file',
            size: file.isdir ? undefined : formatFileSize(file.additional?.size),
            modified: modifiedStr,
            path: file.path
          };
        });
        setFileList(items);

        // Save context for later download operations
        setCurrentSharingContext({ geId, lang });

        // Initialize navigation history with root path
        const rootPath = data.path || '';
        setNavigationHistory([rootPath]);
        setNavigationIndex(0);
        setCurrentPath(rootPath);

        setIsSearching(false);
      } else if (data.status === 'otp_required') {
        // Need OTP - save params and show modal
        setPendingSearchParams({ geId, lang });
        setOtpError(null);
        setIsOtpLoading(false);
        setIsOtpModalOpen(true);
        setIsSearching(false);
      } else {
        // Error - show in modal for critical errors
        showCriticalError(data.message || 'Có lỗi xảy ra khi tìm kiếm file');
        setIsSearching(false);
      }
    } catch (error) {
      showCriticalError('Lỗi kết nối đến server. Vui lòng kiểm tra kết nối mạng.');
      setIsSearching(false);
    }
  }

  const handleOtpSubmit = async (otpCode: string) => {
    setIsOtpLoading(true);
    setOtpError(null);

    try {
      // If there's a custom callback (sharing link), use it
      if (otpCallback) {
        await otpCallback(otpCode);
        setIsOtpModalOpen(false);
        setOtpCallback(null);
        setIsOtpLoading(false);
        return;
      }

      // Otherwise, use GE ID flow (existing logic)
      const response = await fetch('/api/raw-files/auth-otp', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ otp_code: otpCode })
      });

      const data = await response.json();

      if (data.status === 'success') {
        setIsOtpModalOpen(false);
        setIsOtpLoading(false);

        // Retry search with pending params
        if (pendingSearchParams) {
          setIsSearching(true);
          const retryResponse = await fetch('/api/raw-files/list', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              ge_id: pendingSearchParams.geId,
              lang: pendingSearchParams.lang
            })
          });

          const retryData = await retryResponse.json();

          if (retryData.status === 'success') {
            const items: FileSystemItem[] = retryData.files.map((file: any) => {
              // Format modified time
              let modifiedStr: string | undefined = undefined;
              const timeValue = file.additional?.time?.mtime || file.modified || file.time;
              if (timeValue) {
                if (typeof timeValue === 'number') {
                  modifiedStr = new Date(timeValue * 1000).toLocaleString('vi-VN', {
                    year: 'numeric',
                    month: '2-digit',
                    day: '2-digit',
                    hour: '2-digit',
                    minute: '2-digit'
                  });
                } else {
                  modifiedStr = timeValue;
                }
              }

              return {
                id: file.path || file.name,
                name: file.name,
                type: file.isdir ? 'folder' : 'file',
                size: file.isdir ? undefined : formatFileSize(file.additional?.size),
                modified: modifiedStr,
                path: file.path
              };
            });
            setFileList(items);
          } else {
            setRawError(retryData.message || 'Có lỗi xảy ra');
          }
          setIsSearching(false);
          setPendingSearchParams(null);
        }
      } else {
        // OTP failed
        setIsOtpLoading(false);
        throw new Error(data.message || 'OTP không đúng');
      }
    } catch (error) {
      setIsOtpLoading(false);
      setOtpError(error instanceof Error ? error.message : 'Có lỗi xảy ra khi xác thực OTP');
      throw error; // Let OtpModal handle error display
    }
  }

  const handleFolderDoubleClick = async (folder: FileSystemItem) => {
    if (!folder.path) return;

    setIsSearching(true);
    setRawError(null);

    try {
      // Check mode to determine which API to call
      let response;
      let data;

      if (currentMode === 'sharing') {
        // === SHARING LINK MODE: Use sharing-link/list-folder endpoint ===
        response = await fetch('/api/sharing-link/list-folder', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            sharing_id: currentSharingId,
            folder_path: folder.path
          })
        });

        data = await response.json();

        if (data.status === 'success') {
          // Convert sharing link format to FileSystemItem
          const items: FileSystemItem[] = data.files.map((file: any) => {
            // Format modified time
            let modifiedStr: string | undefined = undefined;
            const timeValue = file.additional?.time?.mtime || file.modified || file.time;
            if (timeValue) {
              if (typeof timeValue === 'number') {
                modifiedStr = new Date(timeValue * 1000).toLocaleString('vi-VN', {
                  year: 'numeric',
                  month: '2-digit',
                  day: '2-digit',
                  hour: '2-digit',
                  minute: '2-digit'
                });
              } else {
                modifiedStr = timeValue;
              }
            }

            return {
              id: file.path || file.name,
              name: file.name,
              type: file.is_folder ? 'folder' : 'file',
              size: file.is_folder ? undefined : formatFileSize(file.additional?.size),
              modified: modifiedStr,
              path: file.path
            };
          });

          setFileList(items);
          setSelectedFileIds([]);

          // Update navigation history
          const newHistory = navigationHistory.slice(0, navigationIndex + 1);
          newHistory.push(folder.path);
          setNavigationHistory(newHistory);
          setNavigationIndex(newHistory.length - 1);
          setCurrentPath(folder.path);
        } else {
          showCriticalError(data.message || 'Có lỗi xảy ra khi tải thư mục');
        }

      } else {
        // === API MODE: Use raw-files/list-folder endpoint ===
        const [geId, lang] = rawGeId.trim().split(/\s+/);

        response = await fetch('/api/raw-files/list-folder', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            folder_path: folder.path,
            ge_id: geId,
            lang: lang
          })
        });

        data = await response.json();

        if (data.status === 'success') {
          const items: FileSystemItem[] = data.files.map((file: any) => {
            // Format modified time
            let modifiedStr: string | undefined = undefined;
            const timeValue = file.additional?.time?.mtime || file.modified || file.time;
            if (timeValue) {
              if (typeof timeValue === 'number') {
                modifiedStr = new Date(timeValue * 1000).toLocaleString('vi-VN', {
                  year: 'numeric',
                  month: '2-digit',
                  day: '2-digit',
                  hour: '2-digit',
                  minute: '2-digit'
                });
              } else {
                modifiedStr = timeValue;
              }
            }

            return {
              id: file.path || file.name,
              name: file.name,
              type: file.isdir ? 'folder' : 'file',
              size: file.isdir ? undefined : formatFileSize(file.additional?.size),
              modified: modifiedStr,
              path: file.path
            };
          });

          setFileList(items);
          setSelectedFileIds([]);

          // Update navigation history
          const newHistory = navigationHistory.slice(0, navigationIndex + 1);
          newHistory.push(folder.path);
          setNavigationHistory(newHistory);
          setNavigationIndex(newHistory.length - 1);
          setCurrentPath(folder.path);
        } else if (data.status === 'otp_required') {
          setPendingSearchParams({ geId, lang });
          setOtpError(null);
          setIsOtpLoading(false);
          setIsOtpModalOpen(true);
        } else {
          showCriticalError(data.message || 'Có lỗi xảy ra khi tải thư mục');
        }
      }
    } catch (error) {
      showCriticalError('Lỗi kết nối đến server. Vui lòng kiểm tra kết nối mạng.');
    } finally {
      setIsSearching(false);
    }
  };

  const handleNavigateBack = async () => {
    if (navigationIndex <= 0) return;

    // Clear any pending navigation
    if (navigationTimerRef.current) {
      clearTimeout(navigationTimerRef.current);
    }

    const previousPath = navigationHistory[navigationIndex - 1];

    // Use operation lock and debounce to prevent race conditions
    navigationTimerRef.current = setTimeout(() => {
      executeWithLock(async () => {
        setNavigationIndex(navigationIndex - 1);
        await loadFolderContents(previousPath);
      });
    }, 300);
  };

  const handleNavigateForward = async () => {
    if (navigationIndex >= navigationHistory.length - 1) return;

    // Clear any pending navigation
    if (navigationTimerRef.current) {
      clearTimeout(navigationTimerRef.current);
    }

    const nextPath = navigationHistory[navigationIndex + 1];

    // Use operation lock and debounce to prevent race conditions
    navigationTimerRef.current = setTimeout(() => {
      executeWithLock(async () => {
        setNavigationIndex(navigationIndex + 1);
        await loadFolderContents(nextPath);
      });
    }, 300);
  };

  const loadFolderContents = async (folderPath: string) => {
    setIsSearching(true);

    try {
      // Check mode to determine which API to call
      let response;
      let data;

      if (currentMode === 'sharing') {
        // === SHARING LINK MODE ===
        response = await fetch('/api/sharing-link/list-folder', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            sharing_id: currentSharingId,
            folder_path: folderPath
          })
        });

        data = await response.json();

        if (data.status === 'success') {
          const items: FileSystemItem[] = data.files.map((file: any) => {
            // Format modified time
            let modifiedStr: string | undefined = undefined;
            const timeValue = file.additional?.time?.mtime || file.modified || file.time;
            if (timeValue) {
              if (typeof timeValue === 'number') {
                modifiedStr = new Date(timeValue * 1000).toLocaleString('vi-VN', {
                  year: 'numeric',
                  month: '2-digit',
                  day: '2-digit',
                  hour: '2-digit',
                  minute: '2-digit'
                });
              } else {
                modifiedStr = timeValue;
              }
            }

            return {
              id: file.path || file.name,
              name: file.name,
              type: file.is_folder ? 'folder' : 'file',
              size: file.is_folder ? undefined : formatFileSize(file.additional?.size),
              modified: modifiedStr,
              path: file.path
            };
          });

          setFileList(items);
          setSelectedFileIds([]);
          setCurrentPath(folderPath);
        } else {
          showCriticalError(data.message || 'Có lỗi xảy ra khi tải nội dung');
        }

      } else {
        // === API MODE ===
        const [geId, lang] = rawGeId.trim().split(/\s+/);

        response = await fetch('/api/raw-files/list-folder', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            folder_path: folderPath,
            ge_id: geId,
            lang: lang
          })
        });

        data = await response.json();

        if (data.status === 'success') {
          const items: FileSystemItem[] = data.files.map((file: any) => {
            // Format modified time
            let modifiedStr: string | undefined = undefined;
            const timeValue = file.additional?.time?.mtime || file.modified || file.time;
            if (timeValue) {
              if (typeof timeValue === 'number') {
                modifiedStr = new Date(timeValue * 1000).toLocaleString('vi-VN', {
                  year: 'numeric',
                  month: '2-digit',
                  day: '2-digit',
                  hour: '2-digit',
                  minute: '2-digit'
                });
              } else {
                modifiedStr = timeValue;
              }
            }

            return {
              id: file.path || file.name,
              name: file.name,
              type: file.isdir ? 'folder' : 'file',
              size: file.isdir ? undefined : formatFileSize(file.additional?.size),
              modified: modifiedStr,
              path: file.path
            };
          });

          setFileList(items);
          setSelectedFileIds([]);
          setCurrentPath(folderPath);
        } else {
          showCriticalError(data.message || 'Có lỗi xảy ra khi tải nội dung');
        }
      }
    } catch (error) {
      showCriticalError('Lỗi kết nối đến server. Vui lòng kiểm tra kết nối mạng.');
    } finally {
      setIsSearching(false);
    }
  };

  const handleSaveCustomPath = async () => {
    // Use operation lock to prevent race conditions
    await executeWithLock(async () => {
      if (!currentGeId || !currentLang || !currentPath) {
        console.error('Missing required data for saving custom path');
        return;
      }

      setIsSavingCustomPath(true);

      try {
        const response = await fetch('/api/custom-paths', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            ge_id: currentGeId,
            lang: currentLang,
            custom_path: currentPath
          })
        });

        const data = await response.json();

        if (data.success) {
          setCustomPath(currentPath);
          setHasCustomPath(true);

          // Optional: Show success message
          console.log(`Custom path ${data.is_new ? 'saved' : 'updated'} successfully`);
        } else {
          showCriticalError(data.message || 'Lỗi khi lưu custom path');
        }
      } catch (error) {
        showCriticalError('Lỗi kết nối đến server');
        console.error('Save custom path error:', error);
      } finally {
        setIsSavingCustomPath(false);
      }
    });
  };

  const handleFileSelect = (id: string) => {
    setSelectedFileIds(prev =>
      prev.includes(id) ? prev.filter(fileId => fileId !== id) : [...prev, id]
    );
  };

  const handleStartDownload = async () => {
    // Use operation lock to prevent race conditions
    await executeWithLock(async () => {
      const itemsToDownload = selectedFileIds.length > 0
        ? fileList.filter(item => selectedFileIds.includes(item.id))
        : fileList;

      const filesToDownload = itemsToDownload;
      if (filesToDownload.length === 0) return;

      // Parse GE ID and lang from input
      const [geId, lang] = rawGeId.trim().split(/\s+/);

      // Unified download logic for both modes
      await handleUnifiedDownload({
        mode: currentMode,
        files: filesToDownload,
        geId: geId,
        lang: lang,
        sharingId: currentSharingId,
        sharingContext: currentSharingContext
      });
    });
  };

  /**
   * Unified download handler for both API and Sharing modes.
   * Eliminates code duplication between handleGeIdDownload and handleSharingLinkDownload.
   */
  const handleUnifiedDownload = async (options: {
    mode: 'api' | 'sharing' | null;
    files: FileSystemItem[];
    geId?: string;
    lang?: string;
    sharingId?: string;
    sharingContext?: { geId: string; lang: string } | null;
  }) => {
    const { mode, files, geId, lang, sharingId, sharingContext } = options;

    // Validate mode
    if (!mode) {
      showCriticalError('Chế độ tải xuống không xác định. Vui lòng nhấn lại "Tải setting" hoặc "Tải raw".');
      return;
    }

    // Validate required params for API mode
    if (mode === 'api' && (!geId || !lang)) {
      return; // Silent validation for API mode
    }

    // Show loading spinner
    setIsDownloadButtonLoading(true);
    setTimeout(() => setIsDownloadButtonLoading(false), 500);

    try {
      // Prepare files payload (normalize key names)
      const filesPayload = files.map(file => ({
        name: file.name,
        path: file.id,
        isdir: file.type === 'folder',
        is_folder: file.type === 'folder' // Alias for compatibility
      }));

      // Build request payload based on mode
      let requestPayload: any;
      let endpoint: string;

      if (mode === 'api') {
        endpoint = '/api/batches/api';
        requestPayload = {
          files: filesPayload,
          ge_id: geId,
          lang: lang
        };
      } else {
        // Sharing mode
        endpoint = '/api/batches/sharing';
        requestPayload = {
          sharing_id: sharingId,
          files: filesPayload
        };

        // Add GE ID context if available
        if (sharingContext) {
          requestPayload.ge_id = sharingContext.geId;
          requestPayload.lang = sharingContext.lang;
        }
      }

      // Send request
      const response = await fetch(endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(requestPayload)
      });

      const data = await response.json();

      // Handle response
      if (!data.success) {
        showCriticalError(data.message || 'Có lỗi xảy ra khi tạo batch tải xuống');
      } else {
        // Clear selection after successful batch creation
        setSelectedFileIds([]);
      }
    } catch (error) {
      console.error('Download error:', error);
      showCriticalError('Lỗi kết nối đến server. Vui lòng kiểm tra kết nối mạng.');
    }
  };

  // Helper: Convert FileDownload[] to legacy DownloadQueueItem format for components
  // Memoized to prevent unnecessary re-computation
  const convertToQueueItems = React.useCallback((downloads: FileDownload[]) => {
    const batches = groupFilesByBatch(downloads);
    const items: any[] = [];

    batches.forEach((files, batchId) => {
      const firstFile = files[0];

      const allCompleted = files.every(f => f.status === 'completed');
      const anyDownloading = files.some(f => f.status === 'downloading');
      const batchStatus = anyDownloading ? 'processing' : allCompleted ? 'completed' : 'pending';

      // Calculate batch-level progress
      const totalDownloaded = files.reduce((sum, f) => sum + (f.downloaded_size || 0), 0);
      const totalSize = files.reduce((sum, f) => sum + (f.file_size || 0), 0);
      const batchProgress = totalSize > 0 ? (totalDownloaded / totalSize) * 100 : 0;

      items.push({
        key: batchId,
        name: `${firstFile.ge_id} ${firstFile.lang}`,
        status: batchStatus,
        geId: firstFile.ge_id,
        mode: firstFile.mode,
        jobId: batchId, // Use batch_id as jobId for cancel operations
        // mongodb_path usage:
        // - API mode: MongoDB folder_path
        // - Sharing mode: linkRaw URL
        mongoDbPath: firstFile.mongodb_path,
        sharingUrl: firstFile.mongodb_path, // Same as mongoDbPath for sharing mode
        // Pass full path to component, let FolderPathDisplay extract folder
        destinationPath: firstFile.destination_path ||
          `${firstFile.ge_id}_${firstFile.lang.toUpperCase()}`,
        queuePosition: undefined,
        // Store created_at from first file for sorting
        created_at: firstFile.created_at,
        progressData: {
          total_files: files.length,
          current_file_index: files.filter(f => f.status === 'completed').length,
          // Batch-level progress (aggregated from all files)
          current_file_downloaded: totalDownloaded,
          current_file_total: totalSize > 0 ? totalSize : undefined,
          current_file_progress: batchProgress,
          files_status: files.map(f => ({
            name: f.file_name,
            status: f.status,
            // Simple folder detection: Check if original file_path has NO extension
            // File: /path/file.psd → has extension → false
            // Folder: /path/[식자설정] → no extension → true
            is_folder: f.file_path ? !/\.[^/.]+$/.test(f.file_path) : false,
            size: f.file_size || 0,
            progress: typeof f.progress_percent === 'number' ? f.progress_percent : parseFloat(String(f.progress_percent || 0)),
            downloaded: f.downloaded_size || 0,
            total: f.file_size || 0
          }))
        }
      });
    });

    // Sort batches by created_at DESC (newest first)
    items.sort((a, b) => {
      const dateA = new Date(a.created_at).getTime();
      const dateB = new Date(b.created_at).getTime();
      return dateB - dateA; // DESC order
    });

    return items;
  }, [groupFilesByBatch]);

  // Helper: Convert FileDownload[] to legacy DownloadHistoryEntry format
  // Memoized to prevent unnecessary re-computation
  const convertToHistoryEntries = React.useCallback((downloads: FileDownload[]) => {
    const batches = groupFilesByBatch(downloads);
    const entries: DownloadHistoryEntry[] = [];

    batches.forEach((files, batchId) => {
      const firstFile = files[0];

      const successCount = files.filter(f => f.status === 'completed').length;

      // Calculate duration
      let durationStr = '';
      if (firstFile.started_at && firstFile.completed_at) {
        const start = new Date(firstFile.started_at);
        const end = new Date(firstFile.completed_at);
        const durationMs = end.getTime() - start.getTime();
        const seconds = Math.floor(durationMs / 1000);
        const minutes = Math.floor(seconds / 60);
        const hours = Math.floor(minutes / 60);

        if (hours > 0) {
          durationStr = `${hours}h ${minutes % 60}m`;
        } else if (minutes > 0) {
          durationStr = `${minutes}m ${seconds % 60}s`;
        } else {
          durationStr = `${seconds}s`;
        }
      }

      entries.push({
        id: batchId,
        geIdAndLang: `${firstFile.ge_id} ${firstFile.lang}`,
        timestamp: new Date(firstFile.created_at).toLocaleString('vi-VN') + (durationStr ? ` (${durationStr})` : ''),
        files: files.map(f => ({
          name: f.file_name,
          status: f.status === 'completed' ? 'success' : 'error',
          message: f.error_message || (f.status === 'completed' ? 'Thành công' : 'Lỗi'),
          path: f.destination_path || '',
          file_size: f.file_size  // Add file size from database
        })),
        // destinationPath not used in History display
        destinationPath: firstFile.destination_path || `raw/${firstFile.ge_id}_${firstFile.lang}`,
        // For sharing mode: use mongodb_path (if available) or extract from file_path
        // For API mode: use source_path
        mongoDbPath: (() => {
          if (firstFile.mongodb_path) {
            return firstFile.mongodb_path; // Sharing mode with explicit mongodb path
          }
          if (firstFile.source_path) {
            return firstFile.source_path; // API mode
          }
          // Sharing mode without mongodb_path: extract folder from file_path
          if (firstFile.file_path) {
            const segments = firstFile.file_path.split('/').filter(Boolean);
            segments.pop(); // Remove filename
            return '/' + segments.join('/');
          }
          return undefined;
        })(),
        totalFiles: files.length,
        successCount: successCount
      });
    });

    return entries;
  }, [groupFilesByBatch]);

  // Memoize converted items to prevent UI flickering
  const memoizedQueueItems = React.useMemo(() =>
    convertToQueueItems(activeDownloads),
    [activeDownloads, convertToQueueItems]
  );

  const memoizedHistoryEntries = React.useMemo(() =>
    convertToHistoryEntries(historyDownloads),
    [historyDownloads, convertToHistoryEntries]
  );

  // OLD POLLING LOGIC DELETED - Replaced by single polling useEffect at top

  // Memoized handlers to prevent child component re-renders
  const handleAddUserFromModal = useCallback((usernameToAdd: string) => {
    setUsername(prev => prev.split('\n').filter(Boolean).includes(usernameToAdd) ? prev : (prev.trim() ? `${prev.trim()}\n${usernameToAdd}` : usernameToAdd));
  }, []);

  const handleReorderQueue = useCallback((reorderedWaitingItems: GeIdItem[]) =>
    setQueueItems(prev => [...prev.filter(i => i.status !== 'waiting'), ...reorderedWaitingItems]),
    []);

  const handleDeleteQueueItem = useCallback((keyToDelete: string) =>
    setQueueItems(prev => prev.filter(item => item.key !== keyToDelete)),
    []);

  const handleViewProjectInfo = async () => {
    if (!geIdAndLang.trim()) {
      showCriticalError('Vui lòng nhập GE ID & Lang');
      return;
    }

    // Parse first line only
    const firstLine = geIdAndLang.trim().split('\n')[0];
    const parts = firstLine.split(/\s+/);
    const [geId, lang] = parts;

    if (!geId || !lang) {
      showCriticalError('Format không hợp lệ. Vui lòng nhập theo format: GE_ID LANG');
      return;
    }

    setIsLoadingProjectInfo(true);
    setIsProjectInfoModalOpen(true);
    setProjectInfoData(null);

    try {
      const response = await fetch('/api/project-info', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ geId, lang })
      });

      const data = await response.json();

      if (data.success) {
        setProjectInfoData(data.data);
      } else {
        showCriticalError(data.error || 'Không thể lấy thông tin project');
        setIsProjectInfoModalOpen(false);
      }
    } catch (error: any) {
      showCriticalError('Lỗi kết nối đến server');
      setIsProjectInfoModalOpen(false);
    } finally {
      setIsLoadingProjectInfo(false);
    }
  };

  // Handle delete project member
  const handleDeleteProjectMember = async (projectId: number, email: string, lang?: string) => {
    try {
      const response = await fetch('/api/project-member', {
        method: 'DELETE',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ projectId, email, lang })
      });

      const data = await response.json();

      if (data.success) {
        // Refresh project info
        if (projectInfoData) {
          const updatedMembers = { ...projectInfoData.members };
          delete updatedMembers[email];
          setProjectInfoData({ ...projectInfoData, members: updatedMembers });
        }
      } else {
        showCriticalError(data.error || 'Không thể xóa member');
      }
    } catch (error: any) {
      showCriticalError('Lỗi kết nối đến server');
    }
  };

  const handleDeleteSubmission = useCallback(async (idToDelete: string) => {
    try {
      const response = await fetch(`/api/submissions/${idToDelete}`, { method: 'DELETE' });
      const data = await response.json();

      if (data.success) {
        setSubmissions(prev => prev.filter(sub => sub.id !== idToDelete));
      } else {
        console.error('Failed to delete submission:', data);
      }
    } catch (error) {
      console.error('Failed to delete submission:', error);
    }
  }, []);

  const handleRetrySubmission = useCallback(async (submissionToRetry: Submission, errorGeIds: string[], errorUsernames: string[]) => {
    try {
      // Call API to create new submission with only error GE IDs and error usernames
      const response = await fetch(`/api/submissions/${submissionToRetry.id}/retry`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ errorGeIds, errorUsernames })
      });
      const data = await response.json();

      if (data.success) {
        // Scroll to top to see the new submission processing
        window.scrollTo({ top: 0, behavior: 'smooth' });
      } else {
        console.error('Failed to retry submission:', data);
      }
    } catch (error) {
      console.error('Failed to retry submission:', error);
    }
  }, []);

  const handlePasteSubmission = useCallback((username: string, geIdAndLang: string) => {
    setUsername(username);
    setGeIdAndLang(geIdAndLang);
    window.scrollTo({ top: 0, behavior: 'smooth' });
  }, []);

  const handleDeleteDownloadHistory = useCallback(async (batchId: string) => {
    try {
      await fetch(`/api/batches/${batchId}`, { method: 'DELETE' });
      // Batch sẽ tự biến mất khỏi historyDownloads qua realtime
    } catch (error) {
      console.error('Failed to delete download batch:', error);
    }
  }, []);

  const handleRetryDownloadHistory = useCallback((entry: DownloadHistoryEntry) => {
    setRawGeId(entry.geIdAndLang);
    window.scrollTo({ top: 0, behavior: 'smooth' });
  }, []);

  const handleCancelJob = useCallback(async (batchId: string) => {
    try {
      // Get all files in this batch
      const filesToCancel = allDownloads.filter(d => d.batch_id === batchId);

      // Cancel each file
      for (const file of filesToCancel) {
        await fetch(`/api/downloads/${file.id}`, {
          method: 'PATCH',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ action: 'cancel' })
        });
      }
    } catch (error) {
      console.error('Cancel error:', error);
    }
  }, [allDownloads]);

  return (
    <div className="min-h-screen bg-slate-900 flex flex-col items-center p-4 sm:p-6 lg:p-8">
      <div className="w-full max-w-7xl mx-auto">
        <Navigation currentPage={currentPage} setCurrentPage={setCurrentPage} />

        {currentPage === 'permission' && (
          <main>
            <header className="text-center mb-8"><h1 className="text-4xl font-bold text-white mb-2">Cấp quyền TMS</h1><p className="text-slate-400">Nhập thông tin để cấp quyền và xem lịch sử bên dưới.</p></header>
            <div className="bg-slate-800/50 backdrop-blur-sm p-8 rounded-2xl shadow-2xl border border-slate-700 max-w-4xl mx-auto">
              <SubmissionForm
                username={username}
                setUsername={setUsername}
                geIdAndLang={geIdAndLang}
                setGeIdAndLang={setGeIdAndLang}
                handleSubmit={handlePermissionSubmit}
                onManageUserClick={() => setIsUserModalOpen(true)}
                onViewInfoClick={handleViewProjectInfo}
              />
              {error && <p className="text-red-400 mt-4 text-center">{error}</p>}
            </div>
            <div className="max-w-4xl mx-auto"><QueueStatus currentSubmission={currentSubmission} queueItems={queueItems} pendingSubmissionsCount={pendingSubmissionsCount} onOpenQueueModal={() => setIsQueueModalOpen(true)} /></div>
            <div className="mt-12 max-w-4xl mx-auto"><SubmissionHistory submissions={submissions} onErrorClick={handleErrorClick} onDelete={handleDeleteSubmission} onRetry={handleRetrySubmission} onPaste={handlePasteSubmission} /></div>
          </main>
        )}

        {currentPage === 'rawDownload' && (
          <main>
            <div className="grid grid-cols-1 md:grid-cols-2 gap-8 items-start">
              <div className="bg-slate-800/50 backdrop-blur-sm p-8 rounded-2xl shadow-2xl border border-slate-700 h-[450px] flex items-center">
                <div className="w-full">
                  <RawDownloadForm
                    geIdAndLang={rawGeId}
                    setGeIdAndLang={setRawGeId}
                    isLoading={isSearching}
                    handleSubmit={handleRawSearch}
                    handleRawDownload={handleRawDownload}
                    isRawDownloading={isRawDownloading}
                    relatedProjects={relatedProjects}
                    projectNote={projectNote}
                    currentGeId={currentGeId}
                    currentLang={currentLang}
                  />
                </div>
              </div>
              <div className="h-[450px]">
                <FileList
                  items={fileList}
                  isLoading={isSearching}
                  onDownload={handleStartDownload}
                  selectedIds={selectedFileIds}
                  onSelectItem={handleFileSelect}
                  onFolderDoubleClick={handleFolderDoubleClick}
                  onNavigateBack={handleNavigateBack}
                  onNavigateForward={handleNavigateForward}
                  canNavigateBack={navigationIndex > 0}
                  canNavigateForward={navigationIndex < navigationHistory.length - 1}
                  currentPath={currentPath}
                  currentMode={currentMode}
                  isDownloadButtonLoading={isDownloadButtonLoading}
                  hasCustomPath={hasCustomPath}
                  isCustomPath={currentPath === customPath}
                  onSaveCustomPath={handleSaveCustomPath}
                  isSavingCustomPath={isSavingCustomPath}
                  initialSearchQuery={autoSearchQuery}
                />
              </div>
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-8 mt-8">
              <div className="h-[1000px]">
                <DownloadQueueStatus
                  geId=""
                  queue={memoizedQueueItems}
                  onCancelJob={handleCancelJob}
                  downloadingFilesCount={downloadingFilesCount}
                />
              </div>
              <div className="h-[1000px]">
                <DownloadHistory
                  history={memoizedHistoryEntries}
                  onDelete={handleDeleteDownloadHistory}
                  onRetry={handleRetryDownloadHistory}
                  onErrorClick={handleErrorClick}
                />
              </div>
            </div>
          </main>
        )}

        {currentPage === 'check' && (
          <main>
            <CheckPage />
          </main>
        )}
      </div>

      <UserManagementModal isOpen={isUserModalOpen} onClose={() => setIsUserModalOpen(false)} onAddUser={handleAddUserFromModal} />
      <ProjectInfoModal
        isOpen={isProjectInfoModalOpen}
        onClose={() => setIsProjectInfoModalOpen(false)}
        projectDetails={projectInfoData}
        isLoading={isLoadingProjectInfo}
        onDeleteMember={handleDeleteProjectMember}
      />
      <CustomPathManagerModal
        isOpen={isCustomPathModalOpen}
        onClose={() => setIsCustomPathModalOpen(false)}
        onPathUpdated={async () => {
          // Reload custom path for current GE ID if available
          if (currentGeId) {
            try {
              const response = await fetch(`/api/custom-paths/${encodeURIComponent(currentGeId)}`);
              if (response.ok) {
                const data = await response.json();
                if (data.success && data.custom_path) {
                  setCustomPath(data.custom_path);
                  setHasCustomPath(true);
                } else {
                  setCustomPath(null);
                  setHasCustomPath(false);
                }
              }
            } catch (err) {
              console.error('Failed to reload custom path:', err);
            }
          }
        }}
      />
      <QueueManagementModal isOpen={isQueueModalOpen} onClose={() => setIsQueueModalOpen(false)} queueItems={queueItems} onReorder={handleReorderQueue} onDelete={handleDeleteQueueItem} />
      <ErrorDetailModal isOpen={isErrorDetailModalOpen} onClose={closeErrorModal} errorMessage={errorDetailContent} />
      <OtpModal
        isOpen={isOtpModalOpen}
        onClose={() => {
          setIsOtpModalOpen(false);
          setOtpError(null);
          setIsOtpLoading(false);
        }}
        onSubmit={handleOtpSubmit}
        isLoading={isOtpLoading}
        errorMessage={otpError}
      />

      {/* Notification Manager - handles browser notifications */}
      <NotificationManager />
    </div>
  );
};

export default App;
