import { useCallback, useMemo, useRef, useState } from 'react' import { api, errorMessage } from '../lib/api.js' const HISTORY_KEY = 'metatron.drive-upload-history-v1' const finalStatuses = new Set(['complete', 'failed', 'canceled']) function readHistory() { try { return JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]') } catch { return [] } } export function useUploads(onComplete) { const [uploads, setUploadsState] = useState(readHistory) const fileRefs = useRef(new Map()) const abortRefs = useRef(new Map()) const setUploads = useCallback((updater) => { setUploadsState((current) => { const next = typeof updater === 'function' ? updater(current) : updater localStorage.setItem(HISTORY_KEY, JSON.stringify(next.slice(0, 200))) return next }) }, []) const update = useCallback((id, patch) => { setUploads((current) => current.map((item) => item.id === id ? { ...item, ...patch } : item)) }, [setUploads]) const run = useCallback(async (entry, file) => { const controller = new AbortController() abortRefs.current.set(entry.id, controller) update(entry.id, { status: 'uploading', error: null }) try { await api.upload(file, entry.folderId, { signal: controller.signal, onProgress: (progress) => update(entry.id, { progress: Math.max(0, Math.min(1, progress)) }), }) update(entry.id, { status: 'complete', progress: 1, finishedAt: new Date().toISOString() }) window.desktop.notify('Upload complete', `${entry.name} is now in Metatron.Drive`) onComplete?.(entry.folderId) } catch (requestError) { const canceled = requestError?.code === 'ERR_CANCELED' update(entry.id, { status: canceled ? 'canceled' : 'failed', error: canceled ? 'Canceled by user' : errorMessage(requestError), finishedAt: new Date().toISOString(), }) if (!canceled) window.desktop.notify('Upload failed', `Could not upload ${entry.name}`) } finally { abortRefs.current.delete(entry.id) } }, [onComplete, update]) const enqueue = useCallback((files, folderId) => { for (const file of files) { const id = `${Date.now()}-${crypto.randomUUID()}` const entry = { id, name: file.name, size: file.size, type: file.type || 'application/octet-stream', folderId: folderId || null, createdAt: new Date().toISOString(), status: 'queued', progress: 0, error: null, } fileRefs.current.set(id, file) setUploads((current) => [entry, ...current]) run(entry, file) } }, [run, setUploads]) const cancel = useCallback((id) => abortRefs.current.get(id)?.abort(), []) const retry = useCallback((id) => { const file = fileRefs.current.get(id) const original = uploads.find((item) => item.id === id) if (!file || !original) throw new Error('Select the original file again to retry after restarting the app.') const retryEntry = { ...original, id: `${Date.now()}-${crypto.randomUUID()}`, createdAt: new Date().toISOString(), status: 'queued', progress: 0, error: null, finishedAt: null } fileRefs.current.set(retryEntry.id, file) setUploads((current) => [retryEntry, ...current]) run(retryEntry, file) }, [run, setUploads, uploads]) const remove = useCallback((id) => { abortRefs.current.get(id)?.abort() abortRefs.current.delete(id) fileRefs.current.delete(id) setUploads((current) => current.filter((item) => item.id !== id)) }, [setUploads]) const clearFinished = useCallback(() => { setUploads((current) => current.filter((item) => !finalStatuses.has(item.status))) }, [setUploads]) const active = useMemo(() => uploads.filter((item) => !finalStatuses.has(item.status)), [uploads]) return { uploads, active, enqueue, cancel, retry, remove, clearFinished } }