import { useState, useRef, useEffect, useCallback } from 'react'; import { UseFormReturn } from 'react-hook-form'; import { toast } from 'sonner'; import { focusGroupsApi } from '@/lib/api'; export type AutoSaveStatus = 'idle' | 'saving' | 'saved' | 'error'; interface AutoSaveData { name: string; description: string; objective: string; topic: string; duration: number; llm_model: string; reasoning_effort: string; verbosity: string; participants: string[]; participants_count: number; status: string; date: string; uploadedAssets: string[]; } interface UseFocusGroupAutoSaveOptions { form: UseFormReturn; selectedParticipants: string[]; backendAssets: any[]; draftFocusGroupId: string | null; draftToEdit: any | null; activeTab: string; onDraftIdChange: (id: string) => void; } interface UseFocusGroupAutoSaveReturn { autoSaveStatus: AutoSaveStatus; lastSavedData: AutoSaveData | null; setLastSavedData: (data: AutoSaveData | null) => void; isLoadingDraft: boolean; setIsLoadingDraft: (loading: boolean) => void; } export function useFocusGroupAutoSave({ form, selectedParticipants, backendAssets, draftFocusGroupId, draftToEdit, activeTab, onDraftIdChange, }: UseFocusGroupAutoSaveOptions): UseFocusGroupAutoSaveReturn { const [autoSaveStatus, setAutoSaveStatus] = useState('idle'); const [lastSavedData, setLastSavedData] = useState(null); const [saveRetryCount, setSaveRetryCount] = useState(0); const debouncedSaveTimerRef = useRef(null); const isSavingRef = useRef(false); const isLoadingDraftRef = useRef(false); // Use refs to track previous values to prevent unnecessary saves const prevWatchedFieldsRef = useRef(''); const prevSelectedParticipantsRef = useRef(''); // Expose loading draft state const setIsLoadingDraft = useCallback((loading: boolean) => { isLoadingDraftRef.current = loading; }, []); // Simplified auto-save trigger function const triggerAutoSave = useCallback(() => { if (activeTab !== 'setup' || isLoadingDraftRef.current) return; // Clear existing debounced timer if (debouncedSaveTimerRef.current) { clearTimeout(debouncedSaveTimerRef.current); } // Schedule debounced save debouncedSaveTimerRef.current = setTimeout(async () => { if (isSavingRef.current) return; const values = form.getValues(); const currentData: AutoSaveData = { name: values.focusGroupName || '', description: values.researchBrief || '', objective: values.researchBrief || '', topic: values.discussionTopics || '', duration: values.duration ? parseInt(values.duration) : 60, llm_model: values.llm_model || 'gemini-3-pro-preview', reasoning_effort: values.reasoning_effort || 'medium', verbosity: values.verbosity || 'medium', participants: selectedParticipants, participants_count: selectedParticipants.length, status: 'draft', date: new Date().toISOString(), uploadedAssets: backendAssets.map(a => a.filename || a.original_name || 'unknown') }; if (lastSavedData && JSON.stringify(currentData) === JSON.stringify(lastSavedData)) { return; // No changes } if (!currentData.name) { return; // Don't save without name (required by backend) } isSavingRef.current = true; setAutoSaveStatus('saving'); try { let focusGroupId = draftFocusGroupId || (draftToEdit?.id || draftToEdit?._id); if (!focusGroupId) { const response = await focusGroupsApi.create(currentData); focusGroupId = response.data.focus_group_id || response.data.id || response.data._id; onDraftIdChange(focusGroupId); } else { await focusGroupsApi.update(focusGroupId, currentData); } setLastSavedData(currentData); setAutoSaveStatus('saved'); setSaveRetryCount(0); setTimeout(() => { setAutoSaveStatus('idle'); }, 2000); } catch (error) { console.error("Auto-save failed:", error); setAutoSaveStatus('error'); setSaveRetryCount(prev => prev + 1); if (saveRetryCount < 3) { const retryDelay = Math.pow(2, saveRetryCount) * 2000; setTimeout(() => { triggerAutoSave(); }, retryDelay); } else { toast.error("Auto-save failed", { description: "Your changes may not be saved. Please check your connection.", }); } } finally { isSavingRef.current = false; } }, 2000); }, [activeTab, form, selectedParticipants, backendAssets, draftFocusGroupId, draftToEdit, lastSavedData, saveRetryCount, onDraftIdChange]); // Watch for form field changes const watchedFields = form.watch(); // Effect to handle form field changes and trigger auto-save useEffect(() => { const currentWatchedFields = JSON.stringify(watchedFields); if (activeTab === 'setup' && currentWatchedFields !== prevWatchedFieldsRef.current) { prevWatchedFieldsRef.current = currentWatchedFields; triggerAutoSave(); } }, [watchedFields, activeTab, triggerAutoSave]); // Effect to handle participant changes useEffect(() => { const currentParticipants = JSON.stringify(selectedParticipants); if (activeTab === 'setup' && currentParticipants !== prevSelectedParticipantsRef.current) { prevSelectedParticipantsRef.current = currentParticipants; triggerAutoSave(); } }, [selectedParticipants, activeTab, triggerAutoSave]); // Effect to clear timers when leaving setup tab or component unmounts useEffect(() => { if (activeTab !== 'setup') { if (debouncedSaveTimerRef.current) { clearTimeout(debouncedSaveTimerRef.current); } } return () => { if (debouncedSaveTimerRef.current) { clearTimeout(debouncedSaveTimerRef.current); } }; }, [activeTab]); // Initialize refs for new focus groups useEffect(() => { if (!draftToEdit) { setTimeout(() => { isLoadingDraftRef.current = false; const initialFormState = JSON.stringify(form.getValues()); prevWatchedFieldsRef.current = initialFormState; }, 500); } }, [draftToEdit, form]); return { autoSaveStatus, lastSavedData, setLastSavedData, isLoadingDraft: isLoadingDraftRef.current, setIsLoadingDraft, }; }