'use client' // --------------------------------------------------------------------------- // Upload orchestration hook — manages the Drive→Git upload pipeline // --------------------------------------------------------------------------- import { useState, useRef, useCallback } from 'react' import type { Destination } from '@/lib/constants' import type { FolderEntry } from '@/lib/client-types' import type { FileDiff } from '@/lib/types' import { checkFolderDiffs, uploadDrive, uploadGit } from '@/lib/upload-api' import type { CheckResult } from '@/lib/upload-api' interface UseUploadOrchestratorParams { secret: string setSecretError: (err: string | null) => void entries: FolderEntry[] updateEntry: (index: number, patch: Partial) => void resetEntries: () => void } export function useUploadOrchestrator({ secret, setSecretError, entries, updateEntry, resetEntries, }: UseUploadOrchestratorParams) { const [isUploading, setIsUploading] = useState(false) const [globalError, setGlobalError] = useState(null) const [destination, setDestination] = useState(null) const [overwriteConfirm, setOverwriteConfirm] = useState<{ folderName: string diffs: FileDiff[] } | null>(null) const [noChangesFolder, setNoChangesFolder] = useState(null) const [driveError, setDriveError] = useState<{ error: string folderIndex: number } | null>(null) const abortRef = useRef(null) const checkResultRef = useRef({ exists: false, diffs: [] }) // Refs for values used inside callbacks to avoid stale closures const secretRef = useRef(secret) secretRef.current = secret const destinationRef = useRef(destination) destinationRef.current = destination const entriesRef = useRef(entries) entriesRef.current = entries // ---- Internal: push a single folder to Git ---- const pushGit = useCallback(async (index: number, signal?: AbortSignal) => { const folderEntry = entriesRef.current[index] const dest = destinationRef.current const gitResult = await uploadGit( folderEntry, secretRef.current, dest!, (pct) => updateEntry(index, { progress: 50 + Math.round(pct / 2) }), signal, ) updateEntry(index, { status: gitResult.success ? 'success' : 'error', progress: gitResult.success ? 100 : 0, error: gitResult.success ? undefined : gitResult.error, filename: gitResult.filename, }) }, [updateEntry]) // ---- Main upload flow: Drive first, then Git ---- const proceedUpload = useCallback(async () => { setOverwriteConfirm(null) setIsUploading(true) setGlobalError(null) const controller = new AbortController() abortRef.current = controller const currentEntries = entriesRef.current for (let i = 0; i < currentEntries.length; i++) { if (currentEntries[i].status === 'success') continue if (controller.signal.aborted) break const folderEntry = currentEntries[i] const driveAction = checkResultRef.current.exists ? 'replace' : 'new' // ---- Step 1: Drive upload ---- updateEntry(i, { status: 'uploading', progress: 1, error: undefined, driveStatus: 'uploading', driveError: undefined, }) const driveResult = await uploadDrive( folderEntry, secretRef.current, destinationRef.current!, driveAction as 'new' | 'replace', controller.signal, ) if (!driveResult.success) { updateEntry(i, { driveStatus: 'error', driveError: driveResult.error }) setDriveError({ error: driveResult.error || 'Erreur inconnue', folderIndex: i }) return } updateEntry(i, { driveStatus: 'success', progress: 50 }) // ---- Step 2: Git upload ---- await pushGit(i, controller.signal) } abortRef.current = null setIsUploading(false) }, [updateEntry, pushGit]) // ---- Handlers ---- const handleUpload = useCallback(async () => { if (!secretRef.current.trim()) { setSecretError("La cle d'acces est requise") return } if (!destinationRef.current) { setGlobalError('Veuillez choisir une destination') return } if (entriesRef.current.length === 0) return setSecretError(null) setGlobalError(null) const folder = entriesRef.current[0] try { const check = await checkFolderDiffs( folder, destinationRef.current, secretRef.current, abortRef.current?.signal, ) checkResultRef.current = check if (check.exists) { if (check.diffs.length === 0) { setNoChangesFolder(folder.folderName) return } setOverwriteConfirm({ folderName: folder.folderName, diffs: check.diffs }) return } } catch (err) { const message = err instanceof Error ? err.message : 'Erreur inconnue' setGlobalError(message) return } await proceedUpload() }, [setSecretError, proceedUpload]) const handleDriveContinue = useCallback(async () => { if (!driveError) return const idx = driveError.folderIndex setDriveError(null) updateEntry(idx, { driveStatus: 'skipped' }) const signal = abortRef.current?.signal await pushGit(idx, signal) const currentEntries = entriesRef.current for (let i = idx + 1; i < currentEntries.length; i++) { if (currentEntries[i].status === 'success') continue if (abortRef.current?.signal.aborted) break updateEntry(i, { status: 'uploading', progress: 0, error: undefined, driveStatus: 'skipped', }) await pushGit(i, abortRef.current?.signal) } abortRef.current = null setIsUploading(false) }, [driveError, updateEntry, pushGit]) const handleDriveCancel = useCallback(() => { if (!driveError) return const idx = driveError.folderIndex setDriveError(null) updateEntry(idx, { status: 'error', progress: 0, error: 'Upload annule (Drive echoue)', driveStatus: 'error', }) abortRef.current?.abort() abortRef.current = null setIsUploading(false) }, [driveError, updateEntry]) const handleCancel = useCallback(() => { abortRef.current?.abort() abortRef.current = null setIsUploading(false) }, []) const handleReset = useCallback(() => { resetEntries() setGlobalError(null) setIsUploading(false) setDriveError(null) checkResultRef.current = { exists: false, diffs: [] } }, [resetEntries]) return { isUploading, globalError, setGlobalError, destination, setDestination, overwriteConfirm, setOverwriteConfirm, noChangesFolder, setNoChangesFolder, driveError, handleUpload, handleDriveContinue, handleDriveCancel, handleCancel, handleReset, proceedUpload, } }