/** * ExerciseFactory - Factory for creating DRS exercise modules * * Creates exercise modules that implement DRSExerciseInterface * Provides unified creation, initialization, and lifecycle management */ import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; class ExerciseFactory { constructor() { // Registry of available exercise modules this._moduleRegistry = new Map(); // Module type to class name mapping this._typeMap = { 'vocabulary-flashcards': 'VocabularyModule', 'vocabulary-discovery': 'WordDiscoveryModule', 'text': 'TextModule', 'text-analysis': 'TextAnalysisModule', 'reading': 'TextModule', 'reading-comprehension': 'TextModule', 'reading-comprehension-AI': 'TextAnalysisModule', 'grammar': 'GrammarModule', 'grammar-analysis': 'GrammarAnalysisModule', 'grammar-practice': 'GrammarModule', 'grammar-practice-AI': 'GrammarAnalysisModule', 'translation': 'TranslationModule', 'open-response': 'OpenResponseModule', 'phrase': 'PhraseModule', 'phrase-practice': 'PhraseModule', 'audio': 'AudioModule', 'listening-comprehension': 'AudioModule', 'listening-comprehension-AI': 'AudioModule', 'visual-description': 'ImageModule', 'visual-description-AI': 'ImageModule' }; // Cache for loaded module classes this._classCache = new Map(); } /** * Create an exercise module by type * @param {string} exerciseType - Type of exercise to create * @param {Object} dependencies - Dependencies (orchestrator, llmValidator, prerequisiteEngine, contextMemory) * @param {Object} config - Exercise configuration * @param {Object} content - Exercise content (optional, can be passed later to init) * @returns {Promise} - Exercise module instance */ async create(exerciseType, dependencies, config = {}, content = null) { console.log(`๐Ÿญ ExerciseFactory creating: ${exerciseType}`); // Validate exercise type if (!this._typeMap[exerciseType]) { throw new Error(`Unknown exercise type: ${exerciseType}. Available types: ${Object.keys(this._typeMap).join(', ')}`); } // Get module class name const className = this._typeMap[exerciseType]; console.log(`๐Ÿ“ฆ Resolved to module class: ${className}`); // Load module class (with caching) const ModuleClass = await this._loadModuleClass(className); // Validate dependencies this._validateDependencies(className, dependencies); // Create instance const instance = new ModuleClass( dependencies.orchestrator, dependencies.llmValidator, dependencies.prerequisiteEngine, dependencies.contextMemory ); // Validate interface implementation this._validateInterface(instance, className); // Initialize the module with config and content await instance.init(config, content); console.log(`โœ… ${className} created and initialized`); return instance; } /** * Load module class (with caching) * @param {string} className - Name of the module class * @returns {Promise} - Module class */ async _loadModuleClass(className) { // Check cache first if (this._classCache.has(className)) { console.log(`๐Ÿ“ฆ Using cached class: ${className}`); return this._classCache.get(className); } // Determine file path based on class name let modulePath; if (className === 'WordDiscoveryModule') { modulePath = '../exercise-modules/WordDiscoveryModule.js'; } else if (className.endsWith('AnalysisModule')) { modulePath = `../exercise-modules/${className}.js`; } else { modulePath = `../exercise-modules/${className}.js`; } console.log(`๐Ÿ“‚ Loading module from: ${modulePath}`); try { const module = await import(modulePath); const ModuleClass = module.default; // Cache the class this._classCache.set(className, ModuleClass); return ModuleClass; } catch (error) { throw new Error(`Failed to load module class ${className} from ${modulePath}: ${error.message}`); } } /** * Validate dependencies * @param {string} className - Module class name * @param {Object} dependencies - Dependencies object */ _validateDependencies(className, dependencies) { const required = ['orchestrator', 'prerequisiteEngine']; // Modules that don't need AI validation const noAIModules = ['VocabularyModule', 'WordDiscoveryModule']; // Check required dependencies for (const dep of required) { if (!dependencies[dep]) { throw new Error(`${className} requires dependency: ${dep}`); } } // Check optional dependencies (warn if missing, except for no-AI modules) if (!dependencies.llmValidator && !noAIModules.includes(className)) { console.warn(`โš ๏ธ ${className} missing optional dependency: llmValidator`); } if (!dependencies.contextMemory) { console.warn(`โš ๏ธ ${className} missing optional dependency: contextMemory`); } } /** * Validate that instance implements DRSExerciseInterface * @param {Object} instance - Module instance * @param {string} className - Class name for error messages */ _validateInterface(instance, className) { const requiredMethods = [ 'init', 'render', 'destroy', 'validate', 'getResults', 'handleUserInput', 'markCompleted', 'getProgress', 'getExerciseType', 'getExerciseConfig' ]; const missing = []; for (const method of requiredMethods) { if (typeof instance[method] !== 'function') { missing.push(method); } } if (missing.length > 0) { throw new Error( `${className} does not properly implement DRSExerciseInterface.\n` + `Missing methods: ${missing.join(', ')}\n` + `All DRS modules MUST implement the full interface.` ); } console.log(`โœ… ${className} implements DRSExerciseInterface correctly`); } /** * Get all supported exercise types * @returns {string[]} - Array of exercise type strings */ getSupportedTypes() { return Object.keys(this._typeMap); } /** * Check if exercise type is supported * @param {string} exerciseType - Type to check * @returns {boolean} - True if supported */ isSupported(exerciseType) { return this._typeMap.hasOwnProperty(exerciseType); } /** * Get module class name for exercise type * @param {string} exerciseType - Exercise type * @returns {string} - Class name */ getModuleClassName(exerciseType) { return this._typeMap[exerciseType] || null; } /** * Create exercise and render it immediately * @param {string} exerciseType - Type of exercise * @param {HTMLElement} container - Container element * @param {Object} dependencies - Dependencies * @param {Object} config - Configuration * @param {Object} content - Exercise content * @returns {Promise} - Created and rendered module */ async createAndRender(exerciseType, container, dependencies, config, content) { console.log(`๐ŸŽจ Creating and rendering ${exerciseType}...`); // Create the module with config and content const module = await this.create(exerciseType, dependencies, config, content); // Render it (content already passed in init) await module.render(container); console.log(`โœ… ${exerciseType} created and rendered`); return module; } /** * Batch create multiple modules (for preloading) * @param {Array} exerciseTypes - Array of exercise types to create * @param {Object} dependencies - Shared dependencies * @returns {Promise} - Map of type -> instance */ async batchCreate(exerciseTypes, dependencies) { console.log(`๐Ÿญ Batch creating ${exerciseTypes.length} modules...`); const instances = new Map(); const errors = []; for (const type of exerciseTypes) { try { const instance = await this.create(type, dependencies); instances.set(type, instance); } catch (error) { errors.push({ type, error: error.message }); console.error(`โŒ Failed to create ${type}:`, error); } } if (errors.length > 0) { console.warn(`โš ๏ธ ${errors.length} modules failed to create:`, errors); } console.log(`โœ… Batch created ${instances.size}/${exerciseTypes.length} modules`); return instances; } /** * Destroy module and clean up * @param {DRSExerciseInterface} module - Module to destroy */ async destroy(module) { if (!module) return; try { await module.destroy(); console.log(`๐Ÿงน Module destroyed: ${module.getExerciseType()}`); } catch (error) { console.error('Failed to destroy module:', error); } } /** * Get factory statistics * @returns {Object} - Factory stats */ getStats() { return { supportedTypes: this.getSupportedTypes().length, cachedClasses: this._classCache.size, typeMapping: Object.keys(this._typeMap).length }; } /** * Clear class cache (for hot reload during development) */ clearCache() { this._classCache.clear(); console.log('๐Ÿงน ExerciseFactory class cache cleared'); } } // Export singleton instance const exerciseFactory = new ExerciseFactory(); export default exerciseFactory;