'use client' // --------------------------------------------------------------------------- // Upload orchestration hook — manages the Drive→Git upload pipeline // --------------------------------------------------------------------------- import { useState, useRef, useCallback } from 'react' import type { FolderEntry } from '@/lib/client-types' import type { FileDiff } from '@/lib/types' import { checkFolderDiffs, stageUpload, 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 [isChecking, setIsChecking] = useState(false) const [isResolvingDriveError, setIsResolvingDriveError] = useState(false) const [globalError, setGlobalError] = 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: [] }) const uploadActionRef = useRef(false) const stagingIdRef = useRef(null) // Refs for values used inside callbacks to avoid stale closures const secretRef = useRef(secret) secretRef.current = secret const entriesRef = useRef(entries) entriesRef.current = entries // ---- Internal: push a single folder to Git ---- const pushGit = useCallback(async (index: number, signal?: AbortSignal) => { const stagingId = stagingIdRef.current if (!stagingId) { updateEntry(index, { status: 'error', error: 'Preparation serveur introuvable' }) return } const gitResult = await uploadGit( stagingId, secretRef.current, (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 () => { if (uploadActionRef.current) return uploadActionRef.current = true setOverwriteConfirm(null) setIsChecking(false) setIsUploading(true) setGlobalError(null) const controller = new AbortController() abortRef.current = controller try { 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' const stagingId = stagingIdRef.current if (!stagingId) { updateEntry(i, { status: 'error', error: 'Preparation serveur introuvable' }) return } // ---- Step 1: Drive upload ---- updateEntry(i, { status: 'uploading', progress: 1, error: undefined, driveStatus: 'uploading', driveError: undefined, }) const driveResult = await uploadDrive( stagingId, secretRef.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) } } finally { abortRef.current = null setIsUploading(false) uploadActionRef.current = false } }, [updateEntry, pushGit]) // ---- Handlers ---- const handleUpload = useCallback(async () => { if (uploadActionRef.current || isChecking || isUploading) return if (!secretRef.current.trim()) { setSecretError("La cle d'acces est requise") return } if (entriesRef.current.length === 0) return uploadActionRef.current = true setIsChecking(true) setSecretError(null) setGlobalError(null) const folder = entriesRef.current[0] const controller = new AbortController() abortRef.current = controller try { const staged = await stageUpload(folder, secretRef.current, controller.signal) stagingIdRef.current = staged.stagingId const check = await checkFolderDiffs( staged.stagingId, secretRef.current, controller.signal, ) checkResultRef.current = check if (check.exists) { if (check.diffs.length === 0) { setNoChangesFolder(folder.folderName) uploadActionRef.current = false setIsChecking(false) abortRef.current = null return } setOverwriteConfirm({ folderName: folder.folderName, diffs: check.diffs }) uploadActionRef.current = false setIsChecking(false) abortRef.current = null return } } catch (err) { const message = err instanceof Error ? err.message : 'Erreur inconnue' setGlobalError(message) uploadActionRef.current = false setIsChecking(false) abortRef.current = null return } uploadActionRef.current = false abortRef.current = null await proceedUpload() }, [setSecretError, proceedUpload, isChecking, isUploading]) const handleDriveContinue = useCallback(async () => { if (!driveError || uploadActionRef.current) return uploadActionRef.current = true setIsResolvingDriveError(true) const idx = driveError.folderIndex setDriveError(null) try { 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) } } finally { abortRef.current = null setIsUploading(false) setIsResolvingDriveError(false) uploadActionRef.current = 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 uploadActionRef.current = false setIsChecking(false) setIsResolvingDriveError(false) setIsUploading(false) }, [driveError, updateEntry]) const handleCancel = useCallback(() => { if (isChecking) { abortRef.current?.abort() abortRef.current = null uploadActionRef.current = false setIsChecking(false) return } abortRef.current?.abort() abortRef.current = null uploadActionRef.current = false setIsResolvingDriveError(false) setIsUploading(false) stagingIdRef.current = null }, [isChecking]) const handleReset = useCallback(() => { resetEntries() setGlobalError(null) setIsChecking(false) setIsUploading(false) setIsResolvingDriveError(false) setDriveError(null) checkResultRef.current = { exists: false, diffs: [] } uploadActionRef.current = false stagingIdRef.current = null }, [resetEntries]) return { isUploading, isChecking, isResolvingDriveError, globalError, setGlobalError, overwriteConfirm, setOverwriteConfirm, noChangesFolder, setNoChangesFolder, driveError, handleUpload, handleOverwriteCancel: () => { setOverwriteConfirm(null) uploadActionRef.current = false setIsChecking(false) }, handleDriveContinue, handleDriveCancel, handleCancel, handleReset, proceedUpload, } }