import { useReducer, useEffect, useMemo, useState } from 'react' import { flashcardsService, Flashcard } from '@/lib/services/flashcards' // 重新定義所需的介面 interface CardState extends Flashcard { skipCount: number wrongCount: number isCompleted: boolean originalOrder: number synonyms?: string[] difficultyLevelNumeric: number // 添加缺少的屬性 } interface QuizItem { id: string cardId: string cardData: CardState quizType: 'flip-card' | 'vocab-choice' order: number isCompleted: boolean wrongCount: number skipCount: number } interface ReviewState { quizItems: QuizItem[] score: { correct: number; total: number } isComplete: boolean flashcards: Flashcard[] isLoading: boolean error: string | null pendingWordSubmission: string | null // 等待提交的詞彙ID submittingWords: Set // 正在提交的詞彙ID集合 } type ReviewAction = | { type: 'LOAD_PROGRESS'; payload: ReviewState } | { type: 'ANSWER_TEST_ITEM'; payload: { quizItemId: string; confidence: number } } | { type: 'SKIP_TEST_ITEM'; payload: { quizItemId: string } } | { type: 'RESTART' } | { type: 'LOAD_FLASHCARDS_START' } | { type: 'LOAD_FLASHCARDS_SUCCESS'; payload: { flashcards: Flashcard[]; quizItems: QuizItem[] } } | { type: 'LOAD_FLASHCARDS_ERROR'; payload: { error: string } } | { type: 'WORD_SUBMIT_START'; payload: { cardId: string } } | { type: 'WORD_SUBMIT_SUCCESS'; payload: { cardId: string; nextReviewDate: string } } | { type: 'WORD_SUBMIT_ERROR'; payload: { cardId: string; error: string } } // 工具函數 const sortQuizItemsByPriority = (quizItems: QuizItem[]): QuizItem[] => { return quizItems.sort((a, b) => { if (a.isCompleted && !b.isCompleted) return 1 if (!a.isCompleted && b.isCompleted) return -1 const aDelayScore = a.skipCount + a.wrongCount const bDelayScore = b.skipCount + b.wrongCount if (aDelayScore !== bDelayScore) { return aDelayScore - bDelayScore } return a.order - b.order }) } const generateVocabOptions = (correctWord: string, allCards: CardState[]): string[] => { const allWords = allCards.map(card => card.word).filter(word => word !== correctWord) const shuffledWords = allWords.sort(() => Math.random() - 0.5) const distractors = shuffledWords.slice(0, 3) const options = [correctWord, ...distractors] return options.sort(() => Math.random() - 0.5) } // 檢查詞彙是否完全完成且全部正確 const checkWordCompleteAndCorrect = (cardId: string, quizItems: QuizItem[]): boolean => { const wordQuizItems = quizItems.filter(item => item.cardId === cardId) // 必須有測驗項目 if (wordQuizItems.length === 0) return false // 所有測驗項目都必須完成 const allCompleted = wordQuizItems.every(item => item.isCompleted) // 所有測驗項目都必須沒有錯誤 const allCorrect = wordQuizItems.every(item => item.wrongCount === 0) return allCompleted && allCorrect } // 從 Flashcard 生成 QuizItem 的函數 const generateQuizItemsFromFlashcards = (flashcards: Flashcard[]): QuizItem[] => { const quizItems: QuizItem[] = [] let order = 0 flashcards.forEach((card) => { // 轉換 Flashcard 為 CardState 格式 const cardState: CardState = { ...card, exampleTranslation: card.exampleTranslation || '', // 確保 exampleTranslation 不為 undefined skipCount: 0, wrongCount: 0, isCompleted: false, originalOrder: order / 2, // 原始詞卡的順序 synonyms: [], difficultyLevelNumeric: card.masteryLevel || 1 // 添加缺少的屬性 } // 為每張詞卡生成兩種測驗模式 quizItems.push( { id: `${card.id}-flip-card`, cardId: card.id, cardData: cardState, quizType: 'flip-card', order: order++, isCompleted: false, wrongCount: 0, skipCount: 0 }, { id: `${card.id}-vocab-choice`, cardId: card.id, cardData: cardState, quizType: 'vocab-choice', order: order++, isCompleted: false, wrongCount: 0, skipCount: 0 } ) }) return quizItems } // 內部測驗項目更新函數 const updateQuizItem = ( quizItems: QuizItem[], quizItemId: string, updates: Partial ): QuizItem[] => { return quizItems.map((item) => item.id === quizItemId ? { ...item, ...updates } : item ) } const reviewReducer = (state: ReviewState, action: ReviewAction): ReviewState => { switch (action.type) { case 'LOAD_PROGRESS': return action.payload case 'LOAD_FLASHCARDS_START': return { ...state, isLoading: true, error: null } case 'LOAD_FLASHCARDS_SUCCESS': return { ...state, isLoading: false, error: null, flashcards: action.payload.flashcards, quizItems: action.payload.quizItems } case 'LOAD_FLASHCARDS_ERROR': return { ...state, isLoading: false, error: action.payload.error } case 'ANSWER_TEST_ITEM': { const { quizItemId, confidence } = action.payload const isCorrect = confidence >= 1 // 修正:一般(1分)以上都算答對 const quizItem = state.quizItems.find(item => item.id === quizItemId) if (!quizItem) return state // 修正:只有答對才標記為完成,答錯只增加錯誤次數 const updatedQuizItems = updateQuizItem(state.quizItems, quizItemId, isCorrect ? { isCompleted: true } // 答對:標記完成 : { wrongCount: quizItem.wrongCount + 1 } // 答錯:只增加錯誤次數,不完成 ) const newScore = { correct: state.score.correct + (isCorrect ? 1 : 0), total: state.score.total + 1 } const remainingQuizItems = updatedQuizItems.filter(item => !item.isCompleted) const isComplete = remainingQuizItems.length === 0 // 檢查該詞彙是否完全掌握 const wordCompleteAndCorrect = checkWordCompleteAndCorrect(quizItem.cardId, updatedQuizItems) return { ...state, quizItems: updatedQuizItems, score: newScore, isComplete, // 如果詞彙完全掌握,標記為等待提交 pendingWordSubmission: wordCompleteAndCorrect ? quizItem.cardId : state.pendingWordSubmission } } case 'SKIP_TEST_ITEM': { const { quizItemId } = action.payload const quizItem = state.quizItems.find(item => item.id === quizItemId) if (!quizItem) return state const updatedQuizItems = updateQuizItem(state.quizItems, quizItemId, { skipCount: quizItem.skipCount + 1 }) const remainingQuizItems = updatedQuizItems.filter(item => !item.isCompleted) const isComplete = remainingQuizItems.length === 0 return { ...state, quizItems: updatedQuizItems, score: state.score, isComplete } } case 'RESTART': const restartQuizItems = state.flashcards.length > 0 ? generateQuizItemsFromFlashcards(state.flashcards) : [] return { ...state, quizItems: restartQuizItems, score: { correct: 0, total: 0 }, isComplete: false, pendingWordSubmission: null, submittingWords: new Set() } case 'WORD_SUBMIT_START': { const { cardId } = action.payload const newSubmittingWords = new Set(state.submittingWords) newSubmittingWords.add(cardId) return { ...state, submittingWords: newSubmittingWords, // 清除 pending 狀態,因為已經開始處理 pendingWordSubmission: state.pendingWordSubmission === cardId ? null : state.pendingWordSubmission } } case 'WORD_SUBMIT_SUCCESS': { const { cardId, nextReviewDate } = action.payload const newSubmittingWords = new Set(state.submittingWords) newSubmittingWords.delete(cardId) return { ...state, submittingWords: newSubmittingWords, // 更新該詞彙的下次複習時間 flashcards: state.flashcards.map(card => card.id === cardId ? { ...card, nextReviewDate } : card ) } } case 'WORD_SUBMIT_ERROR': { const { cardId } = action.payload const newSubmittingWords = new Set(state.submittingWords) newSubmittingWords.delete(cardId) return { ...state, submittingWords: newSubmittingWords // 錯誤時不阻塞用戶繼續複習,只記錄 log } } default: return state } } export function useReviewSession() { // 使用 useReducer 統一狀態管理 const [state, dispatch] = useReducer(reviewReducer, { quizItems: [], score: { correct: 0, total: 0 }, isComplete: false, flashcards: [], isLoading: false, error: null, pendingWordSubmission: null, submittingWords: new Set() }) const { quizItems, score, isComplete, flashcards, isLoading, error, pendingWordSubmission, submittingWords } = state // 智能排序獲取當前測驗項目 - 使用 useMemo 優化性能 const sortedQuizItems = useMemo(() => sortQuizItemsByPriority(quizItems), [quizItems]) const incompleteQuizItems = useMemo(() => sortedQuizItems.filter((item: QuizItem) => !item.isCompleted), [sortedQuizItems] ) const currentQuizItem = incompleteQuizItems[0] // 總是選擇優先級最高的未完成測驗項目 const currentCard = currentQuizItem?.cardData // 當前詞卡數據 // 載入後端資料和進度 useEffect(() => { const loadFlashcards = async () => { dispatch({ type: 'LOAD_FLASHCARDS_START' }) try { const response = await flashcardsService.getDueFlashcards(10) if (response.success && response.data) { const flashcards = response.data const quizItems = generateQuizItemsFromFlashcards(flashcards) dispatch({ type: 'LOAD_FLASHCARDS_SUCCESS', payload: { flashcards, quizItems } }) console.log('✅ 成功載入', flashcards.length, '張詞卡') console.log('🎯 生成', quizItems.length, '個測驗項目') } else { dispatch({ type: 'LOAD_FLASHCARDS_ERROR', payload: { error: response.error || '載入詞卡失敗' } }) } } catch (error) { dispatch({ type: 'LOAD_FLASHCARDS_ERROR', payload: { error: error instanceof Error ? error.message : '載入詞卡失敗' } }) } } // 先嘗試載入保存的進度 const savedProgress = localStorage.getItem('review-linear-progress') if (savedProgress) { try { const parsed = JSON.parse(savedProgress) const saveTime = new Date(parsed.timestamp) const now = new Date() const isToday = saveTime.toDateString() === now.toDateString() if (isToday && parsed.quizItems && parsed.flashcards) { dispatch({ type: 'LOAD_PROGRESS', payload: { quizItems: parsed.quizItems, score: parsed.score || { correct: 0, total: 0 }, isComplete: parsed.isComplete || false, flashcards: parsed.flashcards, isLoading: false, error: null, pendingWordSubmission: null, submittingWords: new Set() } }) console.log('📖 載入保存的線性複習進度') return // 如果有保存的進度就不重新載入 } } catch (error) { console.warn('進度載入失敗:', error) localStorage.removeItem('review-linear-progress') } } // 載入新的詞卡資料 loadFlashcards() }, []) // 監聽 pendingWordSubmission,自動提交詞彙完成 useEffect(() => { if (pendingWordSubmission && !submittingWords.has(pendingWordSubmission)) { console.log('🔄 監測到詞彙完成,準備提交:', pendingWordSubmission) submitWordCompletion(pendingWordSubmission) } }, [pendingWordSubmission]) // 保存進度到localStorage const saveProgress = () => { const progress = { quizItems, score, isComplete, flashcards, timestamp: new Date().toISOString() } localStorage.setItem('review-linear-progress', JSON.stringify(progress)) console.log('💾 線性進度已保存') } // 提交詞彙完成到後端(簡化版) const submitWordCompletion = async (cardId: string) => { dispatch({ type: 'WORD_SUBMIT_START', payload: { cardId } }) try { console.log('🎯 詞彙完全掌握,提交到後端:', cardId) // 使用簡化的 API,只需要詞卡 ID const result = await flashcardsService.markWordMastered(cardId) if (result.success && result.data) { dispatch({ type: 'WORD_SUBMIT_SUCCESS', payload: { cardId, nextReviewDate: result.data.nextReviewDate } }) console.log('🎉 詞彙已掌握!複習時間已更新至:', result.data.nextReviewDate) console.log('📅 間隔天數:', result.data.intervalDays, '天') console.log('🏆 成功次數:', result.data.successCount) } else { throw new Error(result.error || '標記詞彙掌握失敗') } } catch (error) { dispatch({ type: 'WORD_SUBMIT_ERROR', payload: { cardId, error: error instanceof Error ? error.message : '標記詞彙掌握失敗' } }) console.warn('❌ 詞彙掌握標記失敗:', error) // 不影響用戶繼續複習 } } // 處理測驗項目答題 const handleAnswer = (confidence: number) => { if (!currentQuizItem) return dispatch({ type: 'ANSWER_TEST_ITEM', payload: { quizItemId: currentQuizItem.id, confidence } }) // 保存進度 setTimeout(() => saveProgress(), 100) } // 處理測驗項目跳過 const handleSkip = () => { if (!currentQuizItem) return dispatch({ type: 'SKIP_TEST_ITEM', payload: { quizItemId: currentQuizItem.id } }) // 保存進度 setTimeout(() => saveProgress(), 100) } // 重新開始 - 重置所有狀態 const handleRestart = () => { dispatch({ type: 'RESTART' }) localStorage.removeItem('review-linear-progress') console.log('🔄 線性複習進度已重置') } // 生成詞彙選擇選項 (使用後端提供的 quizOptions) const vocabOptions = useMemo(() => { if (currentQuizItem?.quizType === 'vocab-choice' && currentCard) { // 優先使用後端提供的 quizOptions if (currentCard.quizOptions && currentCard.quizOptions.length > 0) { // 將正確答案和混淆選項組合,並隨機排序 const allOptions = [currentCard.word, ...currentCard.quizOptions] return allOptions.sort(() => Math.random() - 0.5) } // 後備方案:使用本地生成的選項 const cardStates = quizItems.map(item => item.cardData) return generateVocabOptions(currentCard.word, cardStates) } return [] }, [currentQuizItem, currentCard, flashcards]) return { // 狀態 quizItems, score, isComplete, currentQuizItem, currentCard, vocabOptions, sortedQuizItems, isLoading, error, flashcards, // 計算屬性 totalQuizItems: quizItems.length, completedQuizItems: quizItems.filter(item => item.isCompleted).length, // 動作 handleAnswer, handleSkip, handleRestart } }