import { invoke } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; import { useCallback, useEffect, useState } from "react"; import { getBrowserDisplayName } from "@/lib/browser-utils"; import { dismissToast, showDownloadToast, showErrorToast, showSuccessToast, } from "@/lib/toast-utils"; interface GithubRelease { tag_name: string; assets: { name: string; browser_download_url: string; hash?: string; }[]; published_at: string; is_nightly: boolean; } interface BrowserVersionInfo { version: string; is_prerelease: boolean; date: string; } interface DownloadProgress { browser: string; version: string; downloaded_bytes: number; total_bytes?: number; percentage: number; speed_bytes_per_sec: number; eta_seconds?: number; stage: string; } interface BrowserVersionsResult { versions: string[]; new_versions_count?: number; total_versions_count: number; } export function useBrowserDownload() { const [availableVersions, setAvailableVersions] = useState( [], ); const [downloadedVersions, setDownloadedVersions] = useState([]); const [downloadingBrowsers, setDownloadingBrowsers] = useState>( new Set(), ); const [downloadProgress, setDownloadProgress] = useState(null); const formatTime = useCallback((seconds: number): string => { if (seconds < 60) { return `${Math.round(seconds)}s`; } if (seconds < 3600) { const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.round(seconds % 60); return `${minutes}m ${remainingSeconds}s`; } const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); return `${hours}h ${minutes}m`; }, []); const formatBytes = useCallback((bytes: number): string => { if (bytes === 0) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`; }, []); const loadVersions = useCallback(async (browserStr: string) => { const browserName = getBrowserDisplayName(browserStr); // Use a simple loading state instead of toast for version fetching console.log(`Fetching ${browserName} versions...`); try { const versionInfos = await invoke( "fetch_browser_versions_cached_first", { browserStr }, ); // Convert BrowserVersionInfo to GithubRelease format for compatibility const githubReleases: GithubRelease[] = versionInfos.map( (versionInfo) => ({ tag_name: versionInfo.version, assets: [], published_at: versionInfo.date, is_nightly: versionInfo.is_prerelease, }), ); setAvailableVersions(githubReleases); return githubReleases; } catch (error) { console.error("Failed to load versions:", error); showErrorToast(`Failed to fetch ${browserName} versions`, { description: error instanceof Error ? error.message : "Unknown error occurred", duration: 4000, }); throw error; } }, []); const loadVersionsWithNewCount = useCallback(async (browserStr: string) => { const browserName = getBrowserDisplayName(browserStr); try { // Get versions with new count info and cached detailed info const result = await invoke( "fetch_browser_versions_with_count_cached_first", { browserStr }, ); // Get detailed version info for compatibility const versionInfos = await invoke( "fetch_browser_versions_cached_first", { browserStr }, ); // Convert BrowserVersionInfo to GithubRelease format for compatibility const githubReleases: GithubRelease[] = versionInfos.map( (versionInfo) => ({ tag_name: versionInfo.version, assets: [], published_at: versionInfo.date, is_nightly: versionInfo.is_prerelease, }), ); setAvailableVersions(githubReleases); // Show notification about new versions if any were found if (result.new_versions_count && result.new_versions_count > 0) { showSuccessToast( `Found ${result.new_versions_count} new ${browserName} versions!`, { duration: 3000, description: `Total available: ${result.total_versions_count} versions`, }, ); } return githubReleases; } catch (error) { console.error("Failed to load versions:", error); showErrorToast(`Failed to fetch ${browserName} versions`, { description: error instanceof Error ? error.message : "Unknown error occurred", duration: 4000, }); throw error; } }, []); const loadDownloadedVersions = useCallback(async (browserStr: string) => { try { const downloadedVersions = await invoke( "get_downloaded_browser_versions", { browserStr }, ); setDownloadedVersions(downloadedVersions); return downloadedVersions; } catch (error) { console.error("Failed to load downloaded versions:", error); throw error; } }, []); const downloadBrowser = useCallback( async ( browserStr: string, version: string, suppressNotifications = false, ) => { const browserName = getBrowserDisplayName(browserStr); setDownloadingBrowsers((prev) => new Set(prev).add(browserStr)); try { // Check browser compatibility before attempting download const isSupported = await invoke( "is_browser_supported_on_platform", { browserStr }, ); if (!isSupported) { const supportedBrowsers = await invoke( "get_supported_browsers", ); throw new Error( `${browserName} is not supported on your platform. Supported browsers: ${supportedBrowsers .map(getBrowserDisplayName) .join(", ")}`, ); } await invoke("download_browser", { browserStr, version }); await loadDownloadedVersions(browserStr); } catch (error) { console.error("Failed to download browser:", error); if (!suppressNotifications) { // Dismiss any existing download toast and show error dismissToast(`download-${browserStr}-${version}`); showErrorToast(`Failed to download ${browserName} ${version}`, { description: error instanceof Error ? error.message : "Unknown error occurred", }); } throw error; } finally { setDownloadingBrowsers((prev) => { const next = new Set(prev); next.delete(browserStr); return next; }); } }, [loadDownloadedVersions], ); const isVersionDownloaded = useCallback( (version: string) => { return downloadedVersions.includes(version); }, [downloadedVersions], ); // Check if a browser type is currently downloading const isBrowserDownloading = useCallback( (browserStr: string) => { return downloadingBrowsers.has(browserStr); }, [downloadingBrowsers], ); // Legacy isDownloading for backwards compatibility const isDownloading = downloadingBrowsers.size > 0; // Listen for download progress events useEffect(() => { const unlisten = listen("download-progress", (event) => { const progress = event.payload; setDownloadProgress(progress); const browserName = getBrowserDisplayName(progress.browser); // Show toast with progress if (progress.stage === "downloading") { const speedMBps = ( progress.speed_bytes_per_sec / (1024 * 1024) ).toFixed(1); const etaText = progress.eta_seconds ? formatTime(progress.eta_seconds) : "calculating..."; showDownloadToast(browserName, progress.version, "downloading", { percentage: progress.percentage, speed: speedMBps, eta: etaText, }); } else if (progress.stage === "extracting") { showDownloadToast(browserName, progress.version, "extracting"); } else if (progress.stage === "verifying") { showDownloadToast(browserName, progress.version, "verifying"); } else if (progress.stage === "completed") { showDownloadToast(browserName, progress.version, "completed"); setDownloadProgress(null); } }); return () => { void unlisten.then((fn) => { fn(); }); }; }, [formatTime]); return { availableVersions, downloadedVersions, isDownloading, isBrowserDownloading, downloadingBrowsers, downloadProgress, loadVersions, loadVersionsWithNewCount, loadDownloadedVersions, downloadBrowser, isVersionDownloaded, formatBytes, formatTime, }; }