import { useEffect, useMemo, useState } from 'react'; import { computeSyncPaths } from '@/lib/api/syncfs'; import { fetchTracksForSource } from '@/features/playback/utils/playback/fetchTracksForSource'; import { trackToSyncInfo, type SyncStatus } from '@/features/deviceSync/utils/deviceSyncHelpers'; import type { DeviceSyncSource } from '@/features/deviceSync/store/deviceSyncStore'; export interface DeviceSyncSourceStatusesResult { sourcePathsMap: Map; sourceStatuses: Map; } export function useDeviceSyncSourceStatuses( targetDir: string | null, sources: DeviceSyncSource[], pendingDeletion: string[], deviceFilePaths: string[], ): DeviceSyncSourceStatusesResult { // Map source IDs → computed device paths (for status derivation) const [sourcePathsMap, setSourcePathsMap] = useState>(new Map()); // Compute expected paths for each source (for status comparison) useEffect(() => { if (!targetDir || sources.length === 0) { // React Compiler set-state-in-effect rule: state set from an async result resolved in this effect. // eslint-disable-next-line react-hooks/set-state-in-effect setSourcePathsMap(new Map()); return; } // Path schema is fixed in the Rust backend now — no template parameter. let cancelled = false; (async () => { const map = new Map(); await Promise.all(sources.map(async source => { if (cancelled) return; try { const tracks = await fetchTracksForSource(source); const paths = await computeSyncPaths({ tracks: tracks.map((tr, idx) => trackToSyncInfo( tr, '', source.type === 'playlist' ? { name: source.name, index: idx + 1 } : undefined, )), destDir: targetDir, }); map.set(source.id, paths); } catch { map.set(source.id, []); } })); if (!cancelled) setSourcePathsMap(map); })(); return () => { cancelled = true; }; }, [targetDir, sources]); // Derive sync status per source const sourceStatuses = useMemo(() => { const deviceSet = new Set(deviceFilePaths); const statuses = new Map(); for (const source of sources) { if (pendingDeletion.includes(source.id)) { statuses.set(source.id, 'deletion'); } else { const paths = sourcePathsMap.get(source.id) ?? []; const allSynced = paths.length > 0 && paths.every(p => deviceSet.has(p)); statuses.set(source.id, allSynced ? 'synced' : 'pending'); } } return statuses; }, [sources, pendingDeletion, sourcePathsMap, deviceFilePaths]); return { sourcePathsMap, sourceStatuses }; }