// lib/hooks/useExercises.ts import React, { useState, useCallback, useEffect, useRef } from 'react'; import { useSQLiteContext } from 'expo-sqlite'; import { ExerciseDisplay, ExerciseCategory, Equipment, ExerciseType, BaseExercise, toExerciseDisplay } from '@/types/exercise'; import { LibraryService } from '../db/services/LibraryService'; import { useExerciseRefresh } from '@/lib/stores/libraryStore'; // Filtering types export interface ExerciseFilters { type?: ExerciseType[]; category?: ExerciseCategory[]; equipment?: Equipment[]; tags?: string[]; source?: ('local' | 'powr' | 'nostr')[]; searchQuery?: string; } interface ExerciseStats { totalCount: number; byCategory: Partial>; byType: Partial>; byEquipment: Partial>; } const initialStats: ExerciseStats = { totalCount: 0, byCategory: {}, byType: {}, byEquipment: {}, }; export function useExercises() { const db = useSQLiteContext(); const libraryService = React.useMemo(() => new LibraryService(db), [db]); const { refreshCount, refreshExercises, isLoading, setLoading } = useExerciseRefresh(); const [exercises, setExercises] = useState([]); const [error, setError] = useState(null); const [filters, setFilters] = useState({}); const [stats, setStats] = useState(initialStats); // Add a loaded flag to track if we've successfully loaded exercises at least once const hasLoadedRef = useRef(false); // Define loadExercises before using it in useEffect const loadExercises = useCallback(async (showLoading: boolean = true) => { try { // Only show loading indicator if we haven't loaded before or if explicitly requested if (showLoading && (!hasLoadedRef.current || exercises.length === 0)) { setLoading(true); } const allExercises = await libraryService.getExercises(); setExercises(allExercises); hasLoadedRef.current = true; // Calculate stats const newStats = allExercises.reduce((acc: ExerciseStats, exercise: ExerciseDisplay) => { // Increment total count acc.totalCount++; // Update category stats with type checking if (exercise.category) { acc.byCategory[exercise.category] = (acc.byCategory[exercise.category] || 0) + 1; } // Update type stats with type checking if (exercise.type) { acc.byType[exercise.type] = (acc.byType[exercise.type] || 0) + 1; } // Update equipment stats with type checking if (exercise.equipment) { acc.byEquipment[exercise.equipment] = (acc.byEquipment[exercise.equipment] || 0) + 1; } return acc; }, { totalCount: 0, byCategory: {}, byType: {}, byEquipment: {}, }); setStats(newStats); setError(null); } catch (err) { setError(err instanceof Error ? err : new Error('Failed to load exercises')); } finally { setLoading(false); } }, [libraryService, setLoading, exercises.length]); // Add a silentRefresh method that doesn't show loading indicators const silentRefresh = useCallback(() => { loadExercises(false); }, [loadExercises]); // Load exercises when refreshCount changes useEffect(() => { loadExercises(); }, [refreshCount, loadExercises]); // Filter exercises based on current filters const getFilteredExercises = useCallback(() => { return exercises.filter(exercise => { // Type filter if (filters.type?.length && !filters.type.includes(exercise.type)) { return false; } // Category filter if (filters.category?.length && !filters.category.includes(exercise.category)) { return false; } // Equipment filter if (filters.equipment?.length && exercise.equipment && !filters.equipment.includes(exercise.equipment)) { return false; } // Tags filter if (filters.tags?.length && !exercise.tags.some((tag: string) => filters.tags?.includes(tag))) { return false; } // Source filter if (filters.source?.length && !filters.source.includes(exercise.source)) { return false; } // Search query if (filters.searchQuery) { const query = filters.searchQuery.toLowerCase(); return ( exercise.title.toLowerCase().includes(query) || (exercise.description?.toLowerCase() || '').includes(query) || exercise.tags.some((tag: string) => tag.toLowerCase().includes(query)) ); } return true; }); }, [exercises, filters]); // Create a new exercise const createExercise = useCallback(async (exercise: Omit) => { try { // Create a display version of the exercise with source const displayExercise: Omit = { ...exercise, source: 'local', // Set default source for new exercises isFavorite: false }; const id = await libraryService.addExercise(displayExercise); refreshExercises(); // Use the store's refresh function instead of loading directly return id; } catch (err) { setError(err instanceof Error ? err : new Error('Failed to create exercise')); throw err; } }, [libraryService, refreshExercises]); // Delete an exercise const deleteExercise = useCallback(async (id: string) => { try { await libraryService.deleteExercise(id); refreshExercises(); // Use the store's refresh function } catch (err) { setError(err instanceof Error ? err : new Error('Failed to delete exercise')); throw err; } }, [libraryService, refreshExercises]); // Update an exercise const updateExercise = useCallback(async (id: string, updateData: Partial) => { try { // Get the existing exercise first const existingExercises = await libraryService.getExercises(); const existingExercise = existingExercises.find(ex => ex.id === id); if (!existingExercise) { throw new Error(`Exercise with ID ${id} not found`); } // Delete the old exercise await libraryService.deleteExercise(id); // Prepare the updated exercise data (without id since it's Omit) const updatedExercise: Omit = { ...existingExercise, ...updateData, source: existingExercise.source || 'local', isFavorite: existingExercise.isFavorite || false }; // Remove id property since it's not allowed in this type const { id: _, ...exerciseWithoutId } = updatedExercise as any; // Add the updated exercise with the same ID await libraryService.addExercise(exerciseWithoutId); // Refresh exercises to get the updated list refreshExercises(); return id; } catch (err) { setError(err instanceof Error ? err : new Error('Failed to update exercise')); throw err; } }, [libraryService, refreshExercises]); // Update filters const updateFilters = useCallback((newFilters: Partial) => { setFilters(current => ({ ...current, ...newFilters })); }, []); // Clear all filters const clearFilters = useCallback(() => { setFilters({}); }, []); return { exercises: getFilteredExercises(), loading: isLoading, error, stats, filters, updateFilters, clearFilters, createExercise, deleteExercise, updateExercise, refreshExercises, // Return the refresh function from the store silentRefresh // Add the silent refresh function }; }