import { create } from 'zustand' import { subscribeWithSelector } from 'zustand/middleware' import { getReviewTypesByCEFR } from '@/lib/utils/cefrUtils' import { isTestMode, getTestModeReviewTypes } from '@/lib/mock/reviewMockData' // 複習模式類型 export type ReviewMode = 'flip-memory' | 'vocab-choice' | 'vocab-listening' | 'sentence-listening' | 'sentence-fill' | 'sentence-reorder' | 'sentence-speaking' // 測驗項目接口 export interface TestItem { id: string cardId: string word: string testType: ReviewMode testName: string isCompleted: boolean isCurrent: boolean order: number // 新增狀態欄位 isSkipped: boolean isIncorrect: boolean priority: number skippedAt?: number lastAttemptAt?: number } // 測驗隊列狀態接口 interface TestQueueState { // 測驗隊列狀態 testItems: TestItem[] currentTestIndex: number completedTests: number totalTests: number currentMode: ReviewMode // 新增跳過隊列管理 skippedTests: Set priorityQueue: TestItem[] // Actions setTestItems: (items: TestItem[]) => void setCurrentTestIndex: (index: number) => void setCompletedTests: (completed: number) => void setTotalTests: (total: number) => void setCurrentMode: (mode: ReviewMode) => void initializeTestQueue: (dueCards: any[], completedTests: any[]) => void goToNextTest: () => void skipCurrentTest: () => void markTestCompleted: (testIndex: number) => void markTestIncorrect: (testIndex: number) => void resetQueue: () => void // 新增智能隊列管理方法 reorderByPriority: () => void getNextPriorityTest: () => TestItem | null isAllTestsCompleted: () => boolean getTestStats: () => { total: number completed: number skipped: number incorrect: number remaining: number } } // 工具函數 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 } // 優先級演算法 function calculateTestPriority(test: TestItem): number { const now = Date.now() // 基礎優先級分數 let priority = 0 // 1. 未嘗試的測驗有最高優先級 if (!test.isCompleted && !test.isSkipped && !test.isIncorrect) { priority = 100 } // 2. 答錯的測驗需要重複練習 else if (test.isIncorrect) { priority = 20 // 如果是最近答錯的,稍微降低優先級避免連續重複 if (test.lastAttemptAt && (now - test.lastAttemptAt) < 60000) { // 1分鐘內 priority = 15 } } // 3. 跳過的測驗排在最後 else if (test.isSkipped) { priority = 10 // 跳過時間越久,優先級稍微提高 if (test.skippedAt) { const timeSinceSkipped = now - test.skippedAt const hours = timeSinceSkipped / (1000 * 60 * 60) priority += Math.min(hours * 0.5, 5) // 最多增加5分 } } return priority } function reorderTestItems(testItems: TestItem[]): TestItem[] { // 更新每個測驗的優先級 const itemsWithPriority = testItems.map(item => ({ ...item, priority: calculateTestPriority(item) })) // 按優先級排序 return itemsWithPriority.sort((a, b) => { // 1. 優先級分數高的在前 if (b.priority !== a.priority) { return b.priority - a.priority } // 2. 相同優先級時,按原始順序 return a.order - b.order }) } export const useTestQueueStore = create()( subscribeWithSelector((set, get) => ({ // 初始狀態 testItems: [], currentTestIndex: 0, completedTests: 0, totalTests: 0, currentMode: 'flip-memory', skippedTests: new Set(), priorityQueue: [], // Actions setTestItems: (items) => set({ testItems: items }), setCurrentTestIndex: (index) => set({ currentTestIndex: index }), setCompletedTests: (completed) => set({ completedTests: completed }), setTotalTests: (total) => set({ totalTests: total }), setCurrentMode: (mode) => set({ currentMode: mode }), initializeTestQueue: (dueCards = [], completedTests = []) => { const userCEFRLevel = localStorage.getItem('userEnglishLevel') || 'A2' let remainingTestItems: TestItem[] = [] let order = 1 dueCards.forEach(card => { const wordCEFRLevel = card.cefr || 'A2' // 🧪 測試模式:使用簡化的測驗類型分配 const allTestTypes = isTestMode() ? getTestModeReviewTypes(userCEFRLevel, wordCEFRLevel) : 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, // 新增狀態欄位 isSkipped: false, isIncorrect: false, priority: 100 // 新測驗預設最高優先級 }) order++ }) }) if (remainingTestItems.length === 0) { console.log('🎉 所有測驗都已完成!') return } // 標記第一個測驗為當前 remainingTestItems[0].isCurrent = true set({ testItems: remainingTestItems, totalTests: remainingTestItems.length, currentTestIndex: 0, completedTests: 0, currentMode: remainingTestItems[0].testType, skippedTests: new Set(), priorityQueue: reorderTestItems(remainingTestItems) }) 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] set({ testItems: updatedTestItems, currentTestIndex: nextIndex, currentMode: nextTestItem.testType }) console.log(`🔄 載入下一個測驗: ${nextTestItem.word} - ${nextTestItem.testType}`) } else { console.log('🎉 所有測驗完成!') } }, skipCurrentTest: () => { const { testItems, currentTestIndex, skippedTests } = get() const currentTest = testItems[currentTestIndex] if (!currentTest) return // 標記測驗為跳過狀態 const updatedTest = { ...currentTest, isSkipped: true, skippedAt: Date.now(), isCurrent: false, priority: calculateTestPriority({ ...currentTest, isSkipped: true }) } // 更新跳過測驗集合 const newSkippedTests = new Set(skippedTests) newSkippedTests.add(currentTest.id) // 重新排序隊列 const updatedItems = testItems.map((item, index) => index === currentTestIndex ? updatedTest : item ) const reorderedItems = reorderTestItems(updatedItems) // 找到下一個高優先級測驗 const nextTestIndex = reorderedItems.findIndex(item => !item.isCompleted && item.id !== currentTest.id ) // 標記新的當前測驗 if (nextTestIndex >= 0) { reorderedItems[nextTestIndex].isCurrent = true } set({ testItems: reorderedItems, currentTestIndex: Math.max(0, nextTestIndex), skippedTests: newSkippedTests, priorityQueue: reorderedItems, currentMode: nextTestIndex >= 0 ? reorderedItems[nextTestIndex].testType : 'flip-memory' }) console.log(`⏭️ 跳過測驗: ${currentTest.word} - ${currentTest.testType}`) }, markTestCompleted: (testIndex) => { const { testItems, skippedTests } = get() const completedTest = testItems[testIndex] const updatedTestItems = testItems.map((item, index) => index === testIndex ? { ...item, isCompleted: true, isCurrent: false, priority: 0 } : item ) // 從跳過列表中移除(如果存在) const newSkippedTests = new Set(skippedTests) if (completedTest) { newSkippedTests.delete(completedTest.id) } set({ testItems: updatedTestItems, completedTests: get().completedTests + 1, skippedTests: newSkippedTests, priorityQueue: reorderTestItems(updatedTestItems) }) }, markTestIncorrect: (testIndex) => { const { testItems } = get() const incorrectTest = testItems[testIndex] if (!incorrectTest) return const updatedTest = { ...incorrectTest, isIncorrect: true, lastAttemptAt: Date.now(), isCurrent: false, priority: calculateTestPriority({ ...incorrectTest, isIncorrect: true }) } const updatedItems = testItems.map((item, index) => index === testIndex ? updatedTest : item ) const reorderedItems = reorderTestItems(updatedItems) // 找到下一個測驗 const nextTestIndex = reorderedItems.findIndex(item => !item.isCompleted && item.id !== incorrectTest.id ) if (nextTestIndex >= 0) { reorderedItems[nextTestIndex].isCurrent = true } set({ testItems: reorderedItems, currentTestIndex: Math.max(0, nextTestIndex), priorityQueue: reorderedItems, currentMode: nextTestIndex >= 0 ? reorderedItems[nextTestIndex].testType : 'flip-memory' }) console.log(`❌ 測驗答錯: ${incorrectTest.word} - ${incorrectTest.testType}`) }, resetQueue: () => set({ testItems: [], currentTestIndex: 0, completedTests: 0, totalTests: 0, currentMode: 'flip-memory', skippedTests: new Set(), priorityQueue: [] }), // 新增智能隊列管理方法 reorderByPriority: () => { const { testItems } = get() const reorderedItems = reorderTestItems(testItems) // 找到當前應該顯示的測驗 const currentTestIndex = reorderedItems.findIndex(item => item.isCurrent) const nextAvailableIndex = reorderedItems.findIndex(item => !item.isCompleted) set({ testItems: reorderedItems, priorityQueue: reorderedItems, currentTestIndex: Math.max(0, currentTestIndex >= 0 ? currentTestIndex : nextAvailableIndex) }) }, getNextPriorityTest: () => { const { testItems } = get() return testItems.find(item => !item.isCompleted && !item.isCurrent) || null }, isAllTestsCompleted: () => { const { testItems } = get() return testItems.length > 0 && testItems.every(item => item.isCompleted) }, getTestStats: () => { const { testItems } = get() const stats = { total: testItems.length, completed: testItems.filter(item => item.isCompleted).length, skipped: testItems.filter(item => item.isSkipped && !item.isCompleted).length, incorrect: testItems.filter(item => item.isIncorrect && !item.isCompleted).length, remaining: testItems.filter(item => !item.isCompleted).length } return stats } })) )