import { useState, useCallback, useEffect, useMemo, useRef } from 'react'; import { flashcardsService, type Flashcard } from '@/lib/services/flashcards'; import { useDebounce } from './useDebounce'; // 快取介面定義 interface CacheEntry { data: Flashcard[]; timestamp: Date; filters: { search?: string; favoritesOnly: boolean; partOfSpeech?: string; masteryLevel?: string; }; } // 類型定義 export interface SearchFilters { search: string; cefr: string; partOfSpeech: string; masteryLevel: string; favoritesOnly: boolean; createdAfter?: string; createdBefore?: string; reviewCountMin?: number; reviewCountMax?: number; } export interface SortOptions { sortBy: string; sortOrder: 'asc' | 'desc'; } export interface PaginationState { currentPage: number; pageSize: number; totalPages: number; totalCount: number; hasNext: boolean; hasPrev: boolean; } export interface SearchState { // 資料 flashcards: Flashcard[]; pagination: PaginationState; // UI 狀態 loading: boolean; error: string | null; isInitialLoad: boolean; // 搜尋條件 filters: SearchFilters; sorting: SortOptions; // 元數據 lastUpdated: Date | null; cacheHit: boolean; } export interface SearchActions { // 篩選操作 updateFilters: (filters: Partial) => void; clearFilters: () => void; resetFilters: () => void; // 排序操作 updateSorting: (sorting: Partial) => void; toggleSortOrder: () => void; // 分頁操作 goToPage: (page: number) => void; changePageSize: (size: number) => void; goToNextPage: () => void; goToPrevPage: () => void; // 資料操作 refresh: () => Promise; refetch: () => Promise; clearCache: () => void; } // 初始狀態 const initialState: SearchState = { flashcards: [], pagination: { currentPage: 1, pageSize: 20, totalPages: 0, totalCount: 0, hasNext: false, hasPrev: false, }, loading: false, error: null, isInitialLoad: true, filters: { search: '', cefr: '', partOfSpeech: '', masteryLevel: '', favoritesOnly: false, }, sorting: { sortBy: 'createdAt', sortOrder: 'desc', }, lastUpdated: null, cacheHit: false, }; export const useFlashcardSearch = (activeTab: 'all-cards' | 'favorites' = 'all-cards'): [SearchState, SearchActions] => { const [state, setState] = useState(initialState); // 資料快取 const cacheRef = useRef>(new Map()); // 快取輔助函數 const generateCacheKey = useCallback((filters: any) => { return JSON.stringify({ search: filters.search || '', favoritesOnly: filters.favoritesOnly || false, partOfSpeech: filters.partOfSpeech || '', masteryLevel: filters.masteryLevel || '', activeTab }); }, [activeTab]); const getCachedData = useCallback((filters: any): Flashcard[] | null => { const cacheKey = generateCacheKey(filters); const cached = cacheRef.current.get(cacheKey); if (cached) { // 檢查快取是否過期 (5分鐘) const isExpired = new Date().getTime() - cached.timestamp.getTime() > 300000; if (!isExpired) { return cached.data; } else { cacheRef.current.delete(cacheKey); } } return null; }, [generateCacheKey]); const setCachedData = useCallback((filters: any, data: Flashcard[]) => { const cacheKey = generateCacheKey(filters); cacheRef.current.set(cacheKey, { data, timestamp: new Date(), filters: { search: filters.search, favoritesOnly: filters.favoritesOnly, partOfSpeech: filters.partOfSpeech, masteryLevel: filters.masteryLevel, } }); }, [generateCacheKey]); // 搜尋邏輯 (智能快取版本) const executeSearch = useCallback(async () => { setState(prev => ({ ...prev, loading: true, error: null })); try { // 構建 API 參數 (只包含後端支援的篩選) const apiFilters = { search: state.filters.search || undefined, favoritesOnly: activeTab === 'favorites' || state.filters.favoritesOnly, partOfSpeech: state.filters.partOfSpeech || undefined, masteryLevel: state.filters.masteryLevel || undefined, }; // 檢查快取 const cachedData = getCachedData(apiFilters); let allFlashcards: Flashcard[]; let cacheHit = false; if (cachedData) { // 使用快取資料 allFlashcards = cachedData; cacheHit = true; console.log('🎯 使用快取資料:', allFlashcards.length, '個詞卡'); } else { // API 調用 (只發送後端支援的參數) const result = await flashcardsService.getFlashcards( apiFilters.search, apiFilters.favoritesOnly, undefined, // cefr 客戶端處理 apiFilters.partOfSpeech, apiFilters.masteryLevel, undefined, // sortBy 客戶端處理 undefined, // sortOrder 客戶端處理 1, // 獲取第一頁 1000 // 大數值以獲取所有資料 ); if (result.success && result.data) { allFlashcards = result.data.flashcards; // 快取資料 setCachedData(apiFilters, allFlashcards); console.log('📡 API載入資料:', allFlashcards.length, '個詞卡'); } else { setState(prev => ({ ...prev, loading: false, error: result.error || 'Failed to load flashcards', })); return; } } // 統一處理客戶端篩選和排序 (無論資料來自快取或API) // 客戶端篩選 (因為後端不支援某些篩選功能) if (state.filters.cefr) { allFlashcards = allFlashcards.filter(card => (card as any).cefr === state.filters.cefr ); } // 客戶端排序 (確保排序正確) allFlashcards.sort((a, b) => { let aValue: any, bValue: any; switch (state.sorting.sortBy) { case 'word': aValue = a.word.toLowerCase(); bValue = b.word.toLowerCase(); break; case 'createdAt': aValue = new Date(a.createdAt); bValue = new Date(b.createdAt); break; case 'masteryLevel': aValue = a.masteryLevel; bValue = b.masteryLevel; break; case 'cefr': const levels = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2']; aValue = levels.indexOf((a as any).cefr || 'A1'); bValue = levels.indexOf((b as any).cefr || 'A1'); break; case 'timesReviewed': aValue = a.timesReviewed; bValue = b.timesReviewed; break; default: aValue = new Date(a.createdAt); bValue = new Date(b.createdAt); } if (state.sorting.sortOrder === 'asc') { return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; } else { return aValue > bValue ? -1 : aValue < bValue ? 1 : 0; } }); const totalFilteredCount = allFlashcards.length; // 客戶端分頁處理 const startIndex = (state.pagination.currentPage - 1) * state.pagination.pageSize; const endIndex = startIndex + state.pagination.pageSize; const paginatedFlashcards = allFlashcards.slice(startIndex, endIndex); const totalPages = Math.ceil(totalFilteredCount / state.pagination.pageSize); const currentPage = state.pagination.currentPage; setState(prev => ({ ...prev, flashcards: paginatedFlashcards, pagination: { ...prev.pagination, totalPages, totalCount: totalFilteredCount, hasNext: currentPage < totalPages, hasPrev: currentPage > 1, }, loading: false, isInitialLoad: false, lastUpdated: new Date(), cacheHit, })); } catch (error) { setState(prev => ({ ...prev, loading: false, error: error instanceof Error ? error.message : 'Unknown error', })); } }, [ state.filters.search, state.filters.cefr, state.filters.partOfSpeech, state.filters.masteryLevel, state.filters.favoritesOnly, state.sorting.sortBy, state.sorting.sortOrder, state.pagination.currentPage, state.pagination.pageSize, activeTab ]); // 防抖搜尋 const debouncedSearch = useDebounce(executeSearch, 300); // Actions const updateFilters = useCallback((newFilters: Partial) => { setState(prev => ({ ...prev, filters: { ...prev.filters, ...newFilters }, pagination: { ...prev.pagination, currentPage: 1 }, // 重置頁碼 })); }, []); const clearFilters = useCallback(() => { setState(prev => ({ ...prev, filters: { search: '', cefr: '', partOfSpeech: '', masteryLevel: '', favoritesOnly: false, }, pagination: { ...prev.pagination, currentPage: 1 }, })); }, []); const resetFilters = useCallback(() => { setState(prev => ({ ...prev, filters: initialState.filters, sorting: initialState.sorting, pagination: { ...prev.pagination, currentPage: 1, pageSize: 20 }, })); }, []); const updateSorting = useCallback((newSorting: Partial) => { setState(prev => ({ ...prev, sorting: { ...prev.sorting, ...newSorting }, pagination: { ...prev.pagination, currentPage: 1 }, })); }, []); const toggleSortOrder = useCallback(() => { setState(prev => ({ ...prev, sorting: { ...prev.sorting, sortOrder: prev.sorting.sortOrder === 'asc' ? 'desc' : 'asc' }, pagination: { ...prev.pagination, currentPage: 1 }, })); }, []); const goToPage = useCallback((page: number) => { if (page >= 1 && page <= state.pagination.totalPages) { setState(prev => ({ ...prev, pagination: { ...prev.pagination, currentPage: page }, })); } }, [state.pagination.totalPages]); const changePageSize = useCallback((pageSize: number) => { setState(prev => ({ ...prev, pagination: { ...prev.pagination, pageSize, currentPage: 1 }, })); }, []); const goToNextPage = useCallback(() => { if (state.pagination.hasNext) { goToPage(state.pagination.currentPage + 1); } }, [state.pagination.hasNext, state.pagination.currentPage, goToPage]); const goToPrevPage = useCallback(() => { if (state.pagination.hasPrev) { goToPage(state.pagination.currentPage - 1); } }, [state.pagination.hasPrev, state.pagination.currentPage, goToPage]); const refresh = useCallback(async () => { await executeSearch(); }, [executeSearch]); const refetch = useCallback(async () => { // 清除快取並重新載入 cacheRef.current.clear(); setState(prev => ({ ...prev, isInitialLoad: true })); await executeSearch(); }, [executeSearch]); // 清除快取的公用方法 const clearCache = useCallback(() => { cacheRef.current.clear(); }, []); // 智能觸發搜尋 (區分需要API調用的變更) useEffect(() => { if (state.filters.search) { debouncedSearch(); } else { executeSearch(); } }, [ // 影響後端API的條件 state.filters.search, state.filters.favoritesOnly, state.filters.partOfSpeech, state.filters.masteryLevel, activeTab ]); // 僅客戶端處理的條件變更 (不需重新API調用) useEffect(() => { // 如果資料已載入且只是客戶端篩選/排序/分頁變更,直接處理 if (state.flashcards.length > 0 || !state.isInitialLoad) { executeSearch(); } }, [ state.filters.cefr, // CEFR篩選 state.sorting.sortBy, state.sorting.sortOrder, state.pagination.currentPage, state.pagination.pageSize, ]); // 檢查是否有活動篩選 const hasActiveFilters = useMemo(() => { return !!( state.filters.search || state.filters.cefr || state.filters.partOfSpeech || state.filters.masteryLevel || state.filters.favoritesOnly ); }, [state.filters]); // 增強的狀態 const enhancedState = useMemo(() => ({ ...state, hasActiveFilters, }), [state, hasActiveFilters]); return [ enhancedState, { updateFilters, clearFilters, resetFilters, updateSorting, toggleSortOrder, goToPage, changePageSize, goToNextPage, goToPrevPage, refresh, refetch, clearCache, }, ]; };