/**
 * Persistence Manager Module
 * Handles saving and loading game state and user preferences
 */
import { BaseModule } from './base-module.js';

class PersistenceManagerModule extends BaseModule {
    /**
     * Create a new persistence manager
     */
    constructor() {
        super('persistence-manager', 'Persistence Manager');
        
        // Storage keys
        this.keys = {
            gameState: 'ai-interactive-fiction-state',
            preferences: 'ai-interactive-fiction-preferences',
            saveSlots: 'ai-interactive-fiction-saves'
        };
        
        // Current game state
        this.gameState = null;
        
        // User preferences
        this.preferences = null;
        
        // Save slots
        this.saveSlots = {};
        
        // Default preferences
        this.defaultPreferences = {
            tts: {
                enabled: false,
                preferred_handler: 'none',
                speed: 1.0,
                language: 'en_US',
                voice: '',
                'elevenlabs-tts_api_key': '',
                'elevenlabs-tts_api_url': 'https://api.elevenlabs.io/v1',
                'openai-tts_api_key': '',
                'openai-tts_api_url': 'https://api.openai.com/v1'
            },
            audio: {
                masterVolume: 1.0,
                masterVolumeEnabled: true,
                ttsVolume: 1.0,
                ttsVolumeEnabled: true,
                musicVolume: 0.7,
                musicVolumeEnabled: true,
                sfxVolume: 1.0,
                sfxVolumeEnabled: true,
                musicDuckingAmount: 0.3,
                musicDuckingEnabled: true,
            },
            app: {
                locale: null,
                localeUserOverride: false,
                speed: 1.0,
                autoplay: true,
            }
        };
        
        // Bind methods
        this.bindMethods([
            'saveGameState',
            'loadGameState',
            'savePreferences',
            'loadPreferences',
            'getPreference',
            'updatePreference',
            'resetPreferences',
            'createSaveSlot',
            'loadSaveSlot',
            'deleteSaveSlot',
            'getAllSaveSlots',
            'createBinding',
            'updateElementFromPreference',
            'updatePreferenceFromElement',
            'setupBindings'
        ]);
        
        // Remove circular dependency
        this.dependencies = [];
    }
    
    /**
     * Initialize the module
     * @returns {Promise<boolean>} - Resolves with success status
     */
    async initialize() {
        try {
            this.reportProgress(10, "Initializing persistence manager");
            
            // Load preferences first (with default language settings)
            this.loadPreferences();
            
            // Load save slots
            this.loadSaveSlots();
            
            this.reportProgress(100, "Persistence manager ready");
            return true;
        } catch (error) {
            console.error("Error initializing persistence manager:", error);
            this.reportProgress(100, "Persistence manager failed");
            return false;
        }
    }
    
    /**
     * Save the current game state
     * @param {Object} state - Game state to save
     * @returns {boolean} - Success status
     */
    saveGameState(state) {
        if (!state) return false;
        
        try {
            this.gameState = state;
            localStorage.setItem(this.keys.gameState, JSON.stringify(state));
            
            // Dispatch event
            this.dispatchEvent('game-state-saved', {
                timestamp: new Date().toISOString()
            });
            
            return true;
        } catch (error) {
            console.error("Error saving game state:", error);
            return false;
        }
    }
    
    /**
     * Load the current game state
     * @returns {Object|null} - Loaded game state or null if not found
     */
    loadGameState() {
        try {
            const stateJson = localStorage.getItem(this.keys.gameState);
            if (!stateJson) return null;
            
            this.gameState = JSON.parse(stateJson);
            return this.gameState;
        } catch (error) {
            console.error("Error loading game state:", error);
            return null;
        }
    }
    
    /**
     * Save user preferences
     * @returns {boolean} - Success status
     */
    savePreferences() {
        if (!this.preferences) return false;
        
        try {
            localStorage.setItem(this.keys.preferences, JSON.stringify(this.preferences));
            
            // Dispatch event
            this.dispatchEvent('preferences-saved', {
                timestamp: new Date().toISOString()
            });
            
            return true;
        } catch (error) {
            console.error("Error saving preferences:", error);
            return false;
        }
    }
    
    /**
     * Load user preferences
     * @returns {Object} - Loaded preferences or default preferences if not found
     */
    loadPreferences() {
        try {
            const prefsJson = localStorage.getItem(this.keys.preferences);
            
            if (prefsJson) {
                // Parse stored preferences
                const storedPrefs = JSON.parse(prefsJson);
                
                // Merge with defaults to ensure all keys exist
                this.preferences = this.mergeWithDefaults(storedPrefs, this.defaultPreferences);
            } else {
                // Use defaults if no stored preferences found
                this.preferences = {...this.defaultPreferences};
            }
            
            return this.preferences;
        } catch (error) {
            console.error("Error loading preferences:", error);
            this.preferences = {...this.defaultPreferences};
            return this.preferences;
        }
    }
    
    /**
     * Merge stored preferences with defaults to ensure all keys exist
     * @param {Object} stored - Stored preferences
     * @param {Object} defaults - Default preferences
     * @returns {Object} - Merged preferences
     */
    mergeWithDefaults(stored, defaults) {
        // Base case: if stored is not an object or is null, return defaults
        if (typeof stored !== 'object' || stored === null) {
            return defaults;
        }
        
        // Create a new object to avoid modifying the input objects
        const merged = {};
        
        // Add all keys from defaults, overriding with stored values where they exist
        for (const key in defaults) {
            if (Object.prototype.hasOwnProperty.call(defaults, key)) {
                // If the default value is an object and not null, recurse
                if (typeof defaults[key] === 'object' && defaults[key] !== null) {
                    merged[key] = this.mergeWithDefaults(
                        Object.prototype.hasOwnProperty.call(stored, key) ? stored[key] : {},
                        defaults[key]
                    );
                } else {
                    // Otherwise, use stored value if it exists, otherwise use default
                    merged[key] = Object.prototype.hasOwnProperty.call(stored, key) ? stored[key] : defaults[key];
                }
            }
        }
        
        return merged;
    }
    
    /**
     * Get a specific preference
     * @param {string} category - Preference category
     * @param {string} setting - Preference setting
     * @param {*} defaultValue - Default value if preference not found
     * @returns {*} - Preference value
     */
    getPreference(category, setting, defaultValue = null) {
        if (!category || !setting) return defaultValue;
        
        // Ensure preferences are loaded
        if (!this.preferences) {
            this.loadPreferences();
        }
        
        // Check if category exists
        if (!this.preferences[category]) return defaultValue;
        
        // Check if setting exists in category
        if (!Object.prototype.hasOwnProperty.call(this.preferences[category], setting)) {
            return defaultValue;
        }
        
        return this.preferences[category][setting];
    }
    
    /**
     * Update a specific preference
     * @param {string} category - Preference category
     * @param {string} setting - Preference setting
     * @param {*} value - New value
     * @returns {boolean} - Success status
     */
    updatePreference(category, setting, value) {
        if (!this.preferences) return false;
        
        // Ensure category exists
        if (!this.preferences[category]) {
            this.preferences[category] = {};
        }

        if (Object.prototype.hasOwnProperty.call(this.preferences[category], setting) &&
            Object.is(this.preferences[category][setting], value)) {
            return true;
        }
        
        // Store value
        this.preferences[category][setting] = value;
        
        // Save preferences
        const success = this.savePreferences();
        console.log("Saved preferences: ", category, setting, value, this.preferences)
        
        // Dispatch event
        this.dispatchEvent('preference-updated', {
            category,
            key: setting,
            value,
            timestamp: new Date().toISOString()
        });
        
        return success;
    }
    
    /**
     * Reset preferences to defaults
     * @returns {boolean} - Success status
     */
    resetPreferences() {
        try {
            // Create a deep clone of default preferences
            this.preferences = JSON.parse(JSON.stringify(this.defaultPreferences));
            
            // Save preferences
            const success = this.savePreferences();
            
            // Dispatch event
            this.dispatchEvent('preferences-reset', {
                timestamp: new Date().toISOString()
            });
            
            return success;
        } catch (error) {
            console.error("Error resetting preferences:", error);
            return false;
        }
    }
    
    /**
     * Get all preferences
     * @returns {Object} - All preferences
     */
    getAllPreferences() {
        if (!this.preferences) {
            this.loadPreferences();
        }
        
        return this.preferences;
    }
    
    /**
     * Load save slots
     * @returns {Object} - Save slots
     */
    loadSaveSlots() {
        try {
            const slotsJson = localStorage.getItem(this.keys.saveSlots);
            
            if (slotsJson) {
                this.saveSlots = JSON.parse(slotsJson);
                
                // Validate each save slot
                for (const id in this.saveSlots) {
                    const slot = this.saveSlots[id];
                    if (!slot.id || !slot.name || !slot.timestamp || !slot.state) {
                        delete this.saveSlots[id];
                    }
                }
            } else {
                this.saveSlots = {};
            }
            
            return this.saveSlots;
        } catch (error) {
            console.error("Error loading save slots:", error);
            this.saveSlots = {};
            return this.saveSlots;
        }
    }
    
    /**
     * Save save slots
     * @returns {boolean} - Success status
     */
    saveSaveSlots() {
        try {
            localStorage.setItem(this.keys.saveSlots, JSON.stringify(this.saveSlots));
            return true;
        } catch (error) {
            console.error("Error saving save slots:", error);
            return false;
        }
    }
    
    /**
     * Create a new save slot
     * @param {string} name - Save slot name
     * @param {Object} state - Game state to save
     * @returns {string|null} - Save slot ID or null if failed
     */
    createSaveSlot(name, state) {
        if (!name || !state) return null;
        
        try {
            // Generate unique ID
            const id = `save_${Date.now()}`;
            
            // Create save slot
            this.saveSlots[id] = {
                id,
                name,
                timestamp: new Date().toISOString(),
                state
            };
            
            // Save save slots
            this.saveSaveSlots();
            
            // Dispatch event
            this.dispatchEvent('save-slot-created', {
                id,
                name,
                timestamp: new Date().toISOString()
            });
            
            return id;
        } catch (error) {
            console.error("Error creating save slot:", error);
            return null;
        }
    }
    
    /**
     * Load a save slot
     * @param {string} id - Save slot ID
     * @returns {Object|null} - Game state or null if not found
     */
    loadSaveSlot(id) {
        if (!id || !this.saveSlots[id]) return null;
        
        try {
            const saveSlot = this.saveSlots[id];
            
            // Set as current game state
            this.gameState = saveSlot.state;
            
            // Save current game state
            this.saveGameState(this.gameState);
            
            // Dispatch event
            this.dispatchEvent('save-slot-loaded', {
                id,
                name: saveSlot.name,
                timestamp: new Date().toISOString()
            });
            
            return this.gameState;
        } catch (error) {
            console.error("Error loading save slot:", error);
            return null;
        }
    }
    
    /**
     * Delete a save slot
     * @param {string} id - Save slot ID
     * @returns {boolean} - Success status
     */
    deleteSaveSlot(id) {
        if (!id || !this.saveSlots[id]) return false;
        
        try {
            // Get save slot name before deleting
            const name = this.saveSlots[id].name;
            
            // Delete save slot
            delete this.saveSlots[id];
            
            // Save save slots
            this.saveSaveSlots();
            
            // Dispatch event
            this.dispatchEvent('save-slot-deleted', {
                id,
                name,
                timestamp: new Date().toISOString()
            });
            
            return true;
        } catch (error) {
            console.error("Error deleting save slot:", error);
            return false;
        }
    }
    
    /**
     * Get all save slots
     * @returns {Object} - All save slots
     */
    getAllSaveSlots() {
        if (!this.saveSlots) {
            this.loadSaveSlots();
        }
        
        return this.saveSlots;
    }
    
    /**
     * Create a binding between a DOM element and a preference
     * @param {HTMLElement} element - Element to bind to
     * @param {string} category - Preference category
     * @param {string} key - Preference key
     * @param {Object} options - Additional options (transformers, etc)
     * @returns {Object} - Binding control object
     */
    createBinding(element, category, key, options = {}) {
        if (!element) return null;
        
        const transformer = options.transformer || {
            toElement: (value) => value,
            toPreference: (value) => value
        };
        
        // Store binding info on the element
        element._prefBinding = { category, key, transformer };
        
        // Set initial value
        this.updateElementFromPreference(element);
        
        // Set up event listeners
        const eventHandler = () => this.updatePreferenceFromElement(element);
        
        // Choose appropriate events based on element type
        let events = ['change'];
        if (element.type !== 'checkbox' && element.type !== 'radio' && element.tagName !== 'SELECT') {
            events.push('input');
        }
        
        // Attach event listeners
        events.forEach(event => {
            element.addEventListener(event, eventHandler);
        });
        
        // Return control object
        return {
            update: () => this.updateElementFromPreference(element),
            destroy: () => {
                events.forEach(event => {
                    element.removeEventListener(event, eventHandler);
                });
                delete element._prefBinding;
            }
        };
    }
    
    /**
     * Update an element value from its bound preference
     * @param {HTMLElement} element - The bound element
     */
    updateElementFromPreference(element) {
        if (!element || !element._prefBinding) return;
        
        const { category, key, transformer } = element._prefBinding;
        const value = this.getPreference(category, key);
        const transformedValue = transformer.toElement(value);
        
        // Set element value based on its type
        if (element.type === 'checkbox') {
            element.checked = !!transformedValue;
        } else if (element.type === 'radio') {
            element.checked = element.value === String(transformedValue);
        } else if (element.tagName === 'SELECT') {
            element.value = transformedValue;
        } else {
            element.value = transformedValue;
        }
    }
    
    /**
     * Update a preference from its bound element
     * @param {HTMLElement} element - The bound element
     */
    updatePreferenceFromElement(element) {
        if (!element || !element._prefBinding) return;
        
        const { category, key, transformer } = element._prefBinding;
        let value;
        
        // Get element value based on its type
        if (element.type === 'checkbox') {
            value = element.checked;
        } else if (element.type === 'radio') {
            value = element.checked ? element.value : null;
        } else {
            value = element.value;
        }
        
        const transformedValue = transformer.toPreference(value);
        this.updatePreference(category, key, transformedValue);
    }
    
    /**
     * Set up bindings for all elements with data-pref-bind attributes
     * @param {string} rootSelector - Root selector to search within
     * @returns {Array} - Array of binding control objects
     */
    setupBindings(rootSelector = 'body') {
        const root = document.querySelector(rootSelector);
        if (!root) return [];
        
        const bindings = [];
        const elements = root.querySelectorAll('[data-pref-bind]');
        
        elements.forEach(element => {
            const bindingStr = element.dataset.prefBind;
            if (!bindingStr) return;
            
            const [category, key] = bindingStr.split('.');
            if (!category || !key) return;
            
            // Parse transformer if specified
            let transformer = {
                toElement: (value) => value,
                toPreference: (value) => value
            };
            
            // Handle range transformations
            if (element.type === 'range' && element.hasAttribute('min') && element.hasAttribute('max')) {
                const min = parseInt(element.getAttribute('min'), 10) || 0;
                const max = parseInt(element.getAttribute('max'), 10) || 100;
                
                transformer = {
                    toElement: (value) => {
                        // Convert from 0-1 to min-max
                        return Math.round(value * (max - min) + min);
                    },
                    toPreference: (value) => {
                        // Convert from min-max to 0-1
                        return (parseInt(value, 10) - min) / (max - min);
                    }
                };
            }
            
            // Custom transformer via data attribute
            if (element.dataset.prefTransform) {
                try {
                    // Check if it's a range transformer in format 'range:min,max'
                    if (element.dataset.prefTransform === 'centered-speed') {
                        transformer = {
                            toElement: (value) => Math.round(((Number(value) || 1) * 50) + 50),
                            toPreference: (value) => Math.max(0.5, Math.min(2.0, (parseInt(value, 10) - 50) / 50))
                        };
                    } else if (element.dataset.prefTransform === 'multiplier-percent') {
                        transformer = {
                            toElement: (value) => Math.round((Number(value) || 1) * 100),
                            toPreference: (value) => Math.max(0.25, Math.min(4.0, parseInt(value, 10) / 100))
                        };
                    } else if (element.dataset.prefTransform.startsWith('range:')) {
                        const rangeValues = element.dataset.prefTransform.substring(6).split(',');
                        if (rangeValues.length === 2) {
                            const min = parseFloat(rangeValues[0]);
                            const max = parseFloat(rangeValues[1]);
                            
                            if (!isNaN(min) && !isNaN(max)) {
                                transformer = {
                                    toElement: (value) => {
                                        // Convert from min-max to 0-100 for the slider
                                        return Math.round(((value - min) / (max - min)) * 100);
                                    },
                                    toPreference: (value) => {
                                        // Convert from 0-100 to min-max
                                        return min + (parseInt(value, 10) / 100) * (max - min);
                                    }
                                };
                            }
                        }
                    } else {
                        const customTransformer = JSON.parse(element.dataset.prefTransform);
                        if (customTransformer && typeof customTransformer === 'object') {
                            transformer = customTransformer;
                        }
                    }
                } catch (e) {
                    console.warn('Invalid transformer data attribute', e);
                }
            }
            
            const binding = this.createBinding(element, category, key, { transformer });
            if (binding) {
                bindings.push(binding);
            }
        });
        
        // Set up event listener for preference changes from other sources
        document.addEventListener('preference-updated', (event) => {
            const { category, key } = event.detail;
            
            // Update any matching elements
            elements.forEach(element => {
                if (!element._prefBinding) return;
                
                if (element._prefBinding.category === category && 
                    element._prefBinding.key === key) {
                    this.updateElementFromPreference(element);
                }
            });
        });
        
        return bindings;
    }
    
    /**
     * Clean up when module is disposed
     */
    dispose() {
        // Nothing to clean up
    }
}

// Create the singleton instance
const PersistenceManager = new PersistenceManagerModule();

// Export the module
export { PersistenceManager };
