/**
 * Base Module Class
 * Provides common functionality and enforces a consistent interface for all modules
 */
import { moduleRegistry } from './module-registry.js';

export class BaseModule {
    constructor(id, name) {
        this.id = id;
        this.name = name;
        this.state = 'PENDING';
        this.progress = 0;
        this.progressCallback = null;
        
        // Add standard event target for custom events
        this.eventTarget = document.createElement('div');
        
        // Add standard configuration object
        this.config = {};
        
        // Track event listeners for cleanup
        this._eventListeners = [];
        
        // Resource loading tracking
        this._loadingResources = new Map();
        this._totalResources = 0;
        this._loadedResources = 0;
        
        // Dependencies
        this.dependencies = [];
        this._loadedDependencies = new Map();
        
        // Auto-register with module registry
        moduleRegistry.register(this);
    }

    /**
     * Initialize the module interface
     * @param {Function} progressCallback - Function to report progress
     * @returns {Promise} - Resolves when initialization is complete
     */
    async initializeInterface(progressCallback) {
        this.progressCallback = progressCallback;
        
        try {
            this.changeState('LOADING');
            this.reportProgress(10, "Starting initialization");
            
            // Skip loadDependencies() call - now handled automatically
            
            // Wait for dependencies
            const depStatus = await this._waitForModuleDependencies();
            if (!depStatus) {
                // If dependencies aren't available, report waiting
                this.changeState('WAITING');
                return Promise.resolve(false);
            }
            
            this.changeState('INITIALIZING');
            const initResult = await this.initialize();
            
            if (initResult) {
                this.changeState('FINISHED');
                this.reportProgress(100, "Initialization complete");
            } else {
                this.changeState('ERROR');
                this.reportProgress(100, "Initialization failed");
            }
            
            return initResult;
        } catch (error) {
            console.error(`Error in module ${this.id}:`, error);
            this.changeState('ERROR');
            this.reportProgress(100, "Error during initialization");
            return Promise.resolve(false);
        }
    }
    
    /**
     * Wait for module dependencies
     * @returns {Promise<boolean>} - Resolves when dependencies are ready
     */
    async _waitForModuleDependencies() {
        if (!this.dependencies || this.dependencies.length === 0) {
            return true;
        }
        
        try {
            this.reportProgress(15, "Waiting for dependencies");
            
            // Get moduleRegistry - first try import then fallback to window
            const registry = moduleRegistry;
            
            return this._continueWaitForDependencies(registry);
        } catch (error) {
            console.error(`${this.id}: Error waiting for dependencies:`, error);
            return false;
        }
    }

    /**
     * Continue waiting for dependencies using the provided registry
     * @param {ModuleRegistry} registry - The module registry
     * @returns {Promise<boolean>} - Resolves when dependencies are ready
     * @private
     */
    async _continueWaitForDependencies(registry) {
        try {
            // Wait for all dependencies to be ready
            const results = await registry.waitForModules(this.dependencies);
            
            // Store references to dependencies
            let hasErroredDependencies = false;
            for (let i = 0; i < this.dependencies.length; i++) {
                const depId = this.dependencies[i];
                const depModule = registry.getModule(depId);
                if (depModule) {
                    this._loadedDependencies.set(depId, depModule);
                    
                    // Check if this dependency is in ERROR state
                    if (depModule.state === 'ERROR') {
                        hasErroredDependencies = true;
                        console.warn(`${this.id}: Dependency ${depId} is in ERROR state but will be considered resolved`);
                    }
                }
            }
            
            // Check if all dependencies have resolved (either success or error)
            // We consider a module with ERROR state as resolved
            const allDepsResolved = results.every(result => result === true || result === false);
            if (allDepsResolved) {
                if (hasErroredDependencies) {
                    this.reportProgress(20, "Dependencies resolved with some errors");
                } else {
                    this.reportProgress(20, "Dependencies ready");
                }
                return true;
            } else {
                this.reportProgress(15, "Some dependencies not ready");
                return false;
            }
        } catch (error) {
            console.error(`${this.id}: Error in _continueWaitForDependencies:`, error);
            return false;
        }
    }
    
    /**
     * Initialize the module - Override this in child classes
     * @returns {Promise<boolean>} - Resolves when initialization is complete
     */
    async initialize() {
        return Promise.resolve(true);
    }
    
    /**
     * Change the module state and dispatch an event
     * @param {string} state - The new state
     */
    changeState(state) {
        this.state = state;
        document.dispatchEvent(new ModuleEvent('stateChange', this.id, { state }));
    }
    
    /**
     * Report progress to the module loader
     * @param {number} percent - Progress percentage (0-100)
     * @param {string} message - Status message
     */
    reportProgress(percent, message) {
        this.progress = Math.min(100, Math.max(0, percent));
        
        if (this.progressCallback && typeof this.progressCallback === 'function') {
            this.progressCallback(percent, message);
        } else {
            document.dispatchEvent(new ModuleEvent('progress', this.id, { progress: percent }));
            if (message) {
                document.dispatchEvent(new ModuleEvent('message', this.id, { message }));
            }
        }
    }
    
    /**
     * Get the current module state
     * @returns {string} - Current state
     */
    getState() {
        return this.state;
    }

    /**
     * Get the module ID
     * @returns {string} - Module ID
     */
    getId() {
        return this.id;
    }
    
    /**
     * Get the module name
     * @returns {string} - Module name
     */
    getName() {
        return this.name;
    }

    /**
     * Dispatch a module event
     * @param {string} name - Event name
     * @param {Object} detail - Event details
     */
    dispatchEvent(name, detail = {}) {
        const event = new CustomEvent(name, {
            detail: { moduleId: this.id, ...detail },
            bubbles: true
        });
        document.dispatchEvent(event);
        return event;
    }
    
    /**
     * Add an event listener with automatic tracking for cleanup
     * @param {EventTarget} target - Event target (document, window, etc)
     * @param {string} type - Event type
     * @param {Function} listener - Event listener
     * @param {Object} options - Event listener options
     */
    addEventListener(target, type, listener, options = {}) {
        target.addEventListener(type, listener, options);
        this._eventListeners.push({ target, type, listener, options });
    }
    
    /**
     * Remove a specific event listener
     * @param {EventTarget} target - Event target
     * @param {string} type - Event type
     * @param {Function} listener - Event listener
     * @param {Object} options - Event listener options
     */
    removeEventListener(target, type, listener, options = {}) {
        target.removeEventListener(type, listener, options);
        this._eventListeners = this._eventListeners.filter(
            item => !(item.target === target && 
                    item.type === type && 
                    item.listener === listener)
        );
    }
    
    /**
     * Remove all event listeners registered through addEventListener
     */
    removeAllEventListeners() {
        this._eventListeners.forEach(({ target, type, listener, options }) => {
            target.removeEventListener(type, listener, options);
        });
        this._eventListeners = [];
    }
    
    /**
     * Get a reference to another module
     * @param {string} moduleId - ID of the module to get
     * @returns {BaseModule|null} - The module or null if not found
     */
    getModule(moduleId) {
        // First check our dependency cache
        if (this._loadedDependencies.has(moduleId)) {
            return this._loadedDependencies.get(moduleId);
        }
    }
    
    /**
     * Auto-bind methods to preserve 'this' context
     * @param {Array<string>} methodNames - Array of method names to bind
     */
    bindMethods(methodNames) {
        methodNames.forEach(methodName => {
            if (typeof this[methodName] === 'function') {
                this[methodName] = this[methodName].bind(this);
            } else {
                console.warn(`Method ${methodName} not found on ${this.id} module.`);
            }
        });
    }
    
    /**
     * Update configuration
     * @param {Object} newConfig - New configuration to merge
     */
    updateConfig(newConfig = {}) {
        this.config = { ...this.config, ...newConfig };
    }
    
    /**
     * Get current configuration
     * @returns {Object} - Current configuration
     */
    getConfig() {
        return { ...this.config };
    }

    /**
     * Load a JavaScript file
     * @param {string} url - URL of the script to load
     * @param {boolean} [isModule=false] - Whether to load as a module
     * @returns {Promise<HTMLScriptElement>} - Promise resolving to the loaded script element
     */
    loadScript(url, isModule = false) {
        return new Promise((resolve, reject) => {
            // Track this resource
            this._trackResource(url);
            
            const script = document.createElement('script');
            const cacheBuster = window.MODULE_CACHE_BUSTER;
            if (cacheBuster && /^\/(js|css)\//.test(url)) {
                const separator = url.includes('?') ? '&' : '?';
                script.src = `${url}${separator}v=${encodeURIComponent(cacheBuster)}`;
            } else {
                script.src = url;
            }
            if (isModule) {
                script.type = 'module';
            }
            
            script.onload = () => {
                this._resourceLoaded(url);
                resolve(script);
            };
            
            script.onerror = (error) => {
                this._resourceFailed(url, error);
                reject(new Error(`Failed to load script: ${url}`));
            };
            
            document.head.appendChild(script);
        });
    }

    /**
     * Load a CSS stylesheet
     * @param {string} url - URL of the stylesheet to load
     * @returns {Promise<HTMLLinkElement>} - Promise resolving to the loaded link element
     */
    loadCSS(url) {
        return new Promise((resolve, reject) => {
            // Track this resource
            this._trackResource(url);
            
            const link = document.createElement('link');
            link.href = url;
            link.rel = 'stylesheet';
            
            link.onload = () => {
                this._resourceLoaded(url);
                resolve(link);
            };
            
            link.onerror = (error) => {
                this._resourceFailed(url, error);
                reject(new Error(`Failed to load stylesheet: ${url}`));
            };
            
            document.head.appendChild(link);
        });
    }

    /**
     * Preload an image
     * @param {string} url - URL of the image to load
     * @returns {Promise<HTMLImageElement>} - Promise resolving to the loaded image element
     */
    loadImage(url) {
        return new Promise((resolve, reject) => {
            // Track this resource
            this._trackResource(url);
            
            const img = new Image();
            
            img.onload = () => {
                this._resourceLoaded(url);
                resolve(img);
            };
            
            img.onerror = (error) => {
                this._resourceFailed(url, error);
                reject(new Error(`Failed to load image: ${url}`));
            };
            
            img.src = url;
        });
    }

    /**
     * Load JSON data
     * @param {string} url - URL of the JSON file to load
     * @returns {Promise<Object>} - Promise resolving to the parsed JSON data
     */
    loadJSON(url) {
        return new Promise((resolve, reject) => {
            // Track this resource
            this._trackResource(url);
            
            fetch(url)
                .then(response => {
                    if (!response.ok) {
                        throw new Error(`HTTP error ${response.status}`);
                    }
                    return response.json();
                })
                .then(data => {
                    this._resourceLoaded(url);
                    resolve(data);
                })
                .catch(error => {
                    this._resourceFailed(url, error);
                    reject(new Error(`Failed to load JSON: ${url} - ${error.message}`));
                });
        });
    }

    /**
     * Load a generic resource with fetch
     * @param {string} url - URL of the resource to load
     * @param {string} [responseType='text'] - Response type ('text', 'blob', 'arrayBuffer', etc.)
     * @returns {Promise<any>} - Promise resolving to the loaded resource
     */
    loadResource(url, responseType = 'text') {
        return new Promise((resolve, reject) => {
            // Track this resource
            this._trackResource(url);
            
            fetch(url)
                .then(response => {
                    if (!response.ok) {
                        throw new Error(`HTTP error ${response.status}`);
                    }
                    
                    switch(responseType) {
                        case 'json': return response.json();
                        case 'blob': return response.blob();
                        case 'arrayBuffer': return response.arrayBuffer();
                        case 'formData': return response.formData();
                        case 'text':
                        default: return response.text();
                    }
                })
                .then(data => {
                    this._resourceLoaded(url);
                    resolve(data);
                })
                .catch(error => {
                    this._resourceFailed(url, error);
                    reject(new Error(`Failed to load resource: ${url} - ${error.message}`));
                });
        });
    }

    /**
     * Load multiple resources at once
     * @param {Array<Object>} resources - Array of resource descriptors
     * @returns {Promise<Array>} - Promise resolving to an array of loaded resources
     * @example
     * loadResources([
     *   { type: 'script', url: '/js/lib.js' },
     *   { type: 'css', url: '/css/style.css' },
     *   { type: 'image', url: '/img/logo.png' },
     *   { type: 'json', url: '/data/config.json' }
     * ])
     */
    loadResources(resources) {
        const promises = resources.map(resource => {
            switch(resource.type) {
                case 'script':
                    return this.loadScript(resource.url, resource.isModule);
                case 'css':
                    return this.loadCSS(resource.url);
                case 'image':
                    return this.loadImage(resource.url);
                case 'json':
                    return this.loadJSON(resource.url);
                default:
                    return this.loadResource(resource.url, resource.responseType);
            }
        });
        
        return Promise.all(promises);
    }

    /**
     * Track a resource being loaded
     * @param {string} url - URL of the resource
     * @private
     */
    _trackResource(url) {
        this._loadingResources.set(url, { 
            started: Date.now(),
            completed: false,
            failed: false
        });
        this._totalResources++;
        
        // Report progress
        this._updateResourceProgress();
    }

    /**
     * Mark a resource as successfully loaded
     * @param {string} url - URL of the resource
     * @private
     */
    _resourceLoaded(url) {
        if (this._loadingResources.has(url)) {
            const resource = this._loadingResources.get(url);
            resource.completed = true;
            resource.completedAt = Date.now();
            this._loadedResources++;
            
            // Report progress
            this._updateResourceProgress();
        }
    }

    /**
     * Mark a resource as failed to load
     * @param {string} url - URL of the resource
     * @param {Error} error - Error that occurred
     * @private
     */
    _resourceFailed(url, error) {
        if (this._loadingResources.has(url)) {
            const resource = this._loadingResources.get(url);
            resource.failed = true;
            resource.error = error;
            resource.completedAt = Date.now();
            this._loadedResources++;
            
            // Log the error
            console.error(`${this.id}: Failed to load resource:`, url, error);
            
            // Report progress
            this._updateResourceProgress();
        }
    }

    /**
     * Update loading progress based on resources
     * @private
     */
    _updateResourceProgress() {
        if (this._totalResources === 0) return;
        
        // Change to FETCHING state when loading resources
        if (this.state === 'LOADING' && this._loadedResources === 0) {
            this.changeState('FETCHING');
        }
        
        // Scale resource loading to 10% to 50% range of total module progress
        const percent = Math.round((this._loadedResources / this._totalResources) * 40) + 10;
        this.reportProgress(percent, `Loading resources: ${this._loadedResources}/${this._totalResources}`);
    }
    
    /**
     * Dispose resources when module is destroyed
     * Override in child classes to add custom cleanup
     */
    dispose() {
        this.removeAllEventListeners();
    }
}

/**
 * Module Events - Used for communication between modules and the loader
 */
export class ModuleEvent extends CustomEvent {
    constructor(type, moduleId, data = {}) {
        super(`module:${type}`, {
            detail: {
                moduleId,
                ...data
            },
            bubbles: true
        });
    }
}
