import { create } from 'zustand' import { subscribeWithSelector } from 'zustand/middleware' import { flashcardsService, type Flashcard } from '@/lib/services/flashcards' import { getReviewTypesByCEFR } from '@/lib/utils/cefrUtils' // 複習模式類型 export type ReviewMode = 'flip-memory' | 'vocab-choice' | 'vocab-listening' | 'sentence-listening' | 'sentence-fill' | 'sentence-reorder' | 'sentence-speaking' // 擴展的詞卡接口 export interface ExtendedFlashcard extends Omit { nextReviewDate?: string currentInterval?: number isOverdue?: boolean overdueDays?: number baseMasteryLevel?: number lastReviewDate?: string synonyms?: string[] exampleImage?: string } // 測驗項目接口 export interface TestItem { id: string cardId: string word: string testType: ReviewMode testName: string isCompleted: boolean isCurrent: boolean order: number } // 學習會話狀態 interface LearnState { // 核心狀態 mounted: boolean isLoading: boolean currentCard: ExtendedFlashcard | null dueCards: ExtendedFlashcard[] currentCardIndex: number // 測驗狀態 currentMode: ReviewMode testItems: TestItem[] currentTestIndex: number completedTests: number totalTests: number // 進度狀態 score: { correct: number; total: number } // UI狀態 showComplete: boolean showNoDueCards: boolean // 錯誤狀態 error: string | null // Actions setMounted: (mounted: boolean) => void setLoading: (loading: boolean) => void loadDueCards: () => Promise initializeTestQueue: (completedTests: any[]) => void goToNextTest: () => void recordTestResult: (isCorrect: boolean, userAnswer?: string, confidenceLevel?: number) => Promise skipCurrentTest: () => void resetSession: () => void updateScore: (isCorrect: boolean) => void setError: (error: string | null) => void } export const useLearnStore = create()( subscribeWithSelector((set, get) => ({ // 初始狀態 mounted: false, isLoading: false, currentCard: null, dueCards: [], currentCardIndex: 0, currentMode: 'flip-memory', testItems: [], currentTestIndex: 0, completedTests: 0, totalTests: 0, score: { correct: 0, total: 0 }, showComplete: false, showNoDueCards: false, error: null, // Actions setMounted: (mounted) => set({ mounted }), setLoading: (loading) => set({ isLoading: loading }), loadDueCards: async () => { try { set({ isLoading: true, error: null }) console.log('🔍 開始載入到期詞卡...') const apiResult = await flashcardsService.getDueFlashcards(50) console.log('📡 API回應結果:', apiResult) if (apiResult.success && apiResult.data && apiResult.data.length > 0) { const cards = apiResult.data console.log('✅ 載入後端API數據成功:', cards.length, '張詞卡') set({ dueCards: cards, currentCard: cards[0], currentCardIndex: 0, showNoDueCards: false, showComplete: false }) } else { console.log('❌ 沒有到期詞卡') set({ dueCards: [], currentCard: null, showNoDueCards: true, showComplete: false }) } } catch (error) { console.error('💥 載入到期詞卡失敗:', error) set({ error: '載入詞卡失敗', dueCards: [], currentCard: null, showNoDueCards: true }) } finally { set({ isLoading: false }) } }, initializeTestQueue: (completedTests = []) => { const { dueCards } = get() const userCEFRLevel = localStorage.getItem('userEnglishLevel') || 'A2' let remainingTestItems: TestItem[] = [] let order = 1 dueCards.forEach(card => { const wordCEFRLevel = card.difficultyLevel || 'A2' const allTestTypes = getReviewTypesByCEFR(userCEFRLevel, wordCEFRLevel) const completedTestTypes = completedTests .filter(ct => ct.flashcardId === card.id) .map(ct => ct.testType) const remainingTestTypes = allTestTypes.filter(testType => !completedTestTypes.includes(testType) ) console.log(`🎯 詞卡 ${card.word}: 總共${allTestTypes.length}個測驗, 已完成${completedTestTypes.length}個, 剩餘${remainingTestTypes.length}個`) remainingTestTypes.forEach(testType => { remainingTestItems.push({ id: `${card.id}-${testType}`, cardId: card.id, word: card.word, testType: testType as ReviewMode, testName: getTestTypeName(testType), isCompleted: false, isCurrent: false, order }) order++ }) }) if (remainingTestItems.length === 0) { console.log('🎉 所有測驗都已完成!') set({ showComplete: true }) return } // 標記第一個測驗為當前 remainingTestItems[0].isCurrent = true set({ testItems: remainingTestItems, totalTests: remainingTestItems.length, currentTestIndex: 0, completedTests: 0, currentMode: remainingTestItems[0].testType }) console.log('📝 剩餘測驗項目:', remainingTestItems.length, '個') }, goToNextTest: () => { const { testItems, currentTestIndex } = get() if (currentTestIndex + 1 < testItems.length) { const nextIndex = currentTestIndex + 1 const updatedTestItems = testItems.map((item, index) => ({ ...item, isCurrent: index === nextIndex })) const nextTestItem = updatedTestItems[nextIndex] const { dueCards } = get() const nextCard = dueCards.find(c => c.id === nextTestItem.cardId) set({ testItems: updatedTestItems, currentTestIndex: nextIndex, currentMode: nextTestItem.testType, currentCard: nextCard || null }) console.log(`🔄 載入下一個測驗: ${nextTestItem.word} - ${nextTestItem.testType}`) } else { console.log('🎉 所有測驗完成!') set({ showComplete: true }) } }, recordTestResult: async (isCorrect, userAnswer, confidenceLevel) => { const { testItems, currentTestIndex } = get() const currentTestItem = testItems[currentTestIndex] if (!currentTestItem) return try { console.log('🔄 開始記錄測驗結果...', { flashcardId: currentTestItem.cardId, testType: currentTestItem.testType, isCorrect }) const result = await flashcardsService.recordTestCompletion({ flashcardId: currentTestItem.cardId, testType: currentTestItem.testType, isCorrect, userAnswer, confidenceLevel, responseTimeMs: 2000 }) if (result.success) { console.log('✅ 測驗結果已記錄') // 更新本地狀態 const updatedTestItems = testItems.map((item, index) => index === currentTestIndex ? { ...item, isCompleted: true, isCurrent: false } : item ) set({ testItems: updatedTestItems, completedTests: get().completedTests + 1 }) // 延遲進入下一個測驗 setTimeout(() => { get().goToNextTest() }, 1500) } else { console.error('❌ 記錄測驗結果失敗:', result.error) set({ error: '記錄測驗結果失敗' }) } } catch (error) { console.error('💥 記錄測驗結果異常:', error) set({ error: '記錄測驗結果異常' }) } }, skipCurrentTest: () => { const { testItems, currentTestIndex } = get() const currentTest = testItems[currentTestIndex] if (!currentTest) return // 將當前測驗移到隊列最後 const newItems = [...testItems] newItems.splice(currentTestIndex, 1) newItems.push({ ...currentTest, isCurrent: false }) // 標記新的當前項目 if (newItems[currentTestIndex]) { newItems[currentTestIndex].isCurrent = true } set({ testItems: newItems }) console.log(`⏭️ 跳過測驗: ${currentTest.word} - ${currentTest.testType}`) }, updateScore: (isCorrect) => { set(state => ({ score: { correct: isCorrect ? state.score.correct + 1 : state.score.correct, total: state.score.total + 1 } })) }, resetSession: () => { set({ currentCard: null, dueCards: [], currentCardIndex: 0, currentMode: 'flip-memory', testItems: [], currentTestIndex: 0, completedTests: 0, totalTests: 0, score: { correct: 0, total: 0 }, showComplete: false, showNoDueCards: false, error: null }) }, setError: (error) => set({ error }) })) ) // 工具函數 function getTestTypeName(testType: string): string { const names = { 'flip-memory': '翻卡記憶', 'vocab-choice': '詞彙選擇', 'sentence-fill': '例句填空', 'sentence-reorder': '例句重組', 'vocab-listening': '詞彙聽力', 'sentence-listening': '例句聽力', 'sentence-speaking': '例句口說' } return names[testType as keyof typeof names] || testType }