/**
 * Socket Client Module
 * Handles WebSocket communication for receiving text fragments and game state
 */
import { BaseModule } from './base-module.js';
import { moduleRegistry } from './module-registry.js';

class SocketClientModule extends BaseModule {
    constructor() {
        super('socket-client', 'Socket Client');
        this.socket = null;
        this.textBuffer = null;
        this.isConnected = false;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 5;
        this.reconnectDelay = 2000; // 2 seconds
        this.url = null;
        this.eventListeners = {};
        this.defaultHost = 'localhost:3000'; // Default to localhost:3000 if not running in same origin
    }
    
    /**
     * Load module dependencies
     * @returns {Promise} - Resolves when dependencies are loaded
     */
    async loadDependencies() {
        try {
            // We depend on the text-buffer module
            this.reportProgress(30, "Waiting for text buffer");
            
            // Dynamically load Socket.IO client if not already loaded
            if (!window.io) {
                this.reportProgress(40, "Loading Socket.IO client");
                await this.loadSocketIO();
                this.reportProgress(45, "Socket.IO client loaded");
            }
            
            return true;
        } catch (error) {
            console.error("Error loading Socket Client dependencies:", error);
            return false;
        }
    }
    
    /**
     * Load Socket.IO client library
     * @returns {Promise<void>}
     */
    loadSocketIO() {
        return new Promise((resolve, reject) => {
            // Check if Socket.IO is already loaded
            if (typeof window.io !== 'undefined') {
                resolve();
                return;
            }
            
            // Load the Socket.IO client from the same server that served this page
            const script = document.createElement('script');
            script.src = '/socket.io/socket.io.js'; // Socket.IO automatically serves this
            script.async = true;
            
            script.onload = () => {
                if (typeof window.io !== 'undefined') {
                    resolve();
                } else {
                    reject(new Error('Failed to load Socket.IO client'));
                }
            };
            
            script.onerror = () => {
                reject(new Error('Failed to load Socket.IO client script'));
            };
            
            document.head.appendChild(script);
        });
    }
    
    /**
     * Wait for dependencies to be ready
     */
    async waitForDependencies() {
        try {
            // Wait for the text buffer module to be available
            const textBufferReady = await moduleRegistry.waitForModule('text-buffer', 10000);
            
            if (textBufferReady) {
                this.textBuffer = window.TextBuffer;
                this.reportProgress(60, "Text buffer module ready");
                return true;
            } else {
                console.warn("Text buffer module not ready, Socket Client will have limited functionality");
                return true; // Continue anyway for graceful degradation
            }
        } catch (error) {
            console.error("Error waiting for dependencies:", error);
            return false;
        }
    }
    
    /**
     * Initialize the module
     * @returns {Promise<boolean>} - Resolves with success status
     */
    async initialize() {
        try {
            // Use the current origin for the socket connection
            // This automatically handles the Docker port mapping situation
            const currentUrl = window.location.origin;
            
            console.log(`Socket Client: Using origin for connection: ${currentUrl}`);
            this.url = currentUrl;
            
            this.reportProgress(100, "Socket client initialized");
            return true;
        } catch (error) {
            console.error("Error initializing Socket Client:", error);
            return false;
        }
    }
    
    /**
     * Connect to the Socket.IO server
     * @param {string} url - Optional custom WebSocket URL
     * @returns {Promise<boolean>} - Resolves with connection success
     */
    connect(url = null) {
        return new Promise((resolve) => {
            if (this.isConnected) {
                resolve(true);
                return;
            }
            
            // Use provided URL or default
            const socketUrl = url || this.url;
            
            try {
                console.log(`Socket Client: Connecting to ${socketUrl}`);
                
                // Create Socket.IO connection (will automatically use /socket.io endpoint)
                this.socket = window.io(socketUrl, {
                    reconnection: false, // We handle reconnection ourselves
                    transports: ['websocket', 'polling'] // Prefer WebSocket
                });
                
                this.socket.on('connect', () => {
                    console.log('Socket Client: Connected to server with ID:', this.socket.id);
                    this.isConnected = true;
                    this.reconnectAttempts = 0;
                    this.emitEvent('connect');
                    resolve(true);
                });
                
                this.socket.on('disconnect', (reason) => {
                    console.log(`Socket Client: Connection closed: ${reason}`);
                    this.isConnected = false;
                    this.emitEvent('disconnect', reason);
                    this.attemptReconnect();
                    resolve(false);
                });
                
                this.socket.on('connect_error', (error) => {
                    console.error('Socket Client: Connection error:', error);
                    this.emitEvent('connect_error', error);
                    resolve(false);
                });
                
                // Set up game-specific event handlers
                this.setupGameEventHandlers();
                
            } catch (error) {
                console.error('Socket Client: Connection error:', error);
                this.emitEvent('connect_error', error);
                resolve(false);
            }
        });
    }
    
    /**
     * Set up event handlers for game-specific Socket.IO events
     */
    setupGameEventHandlers() {
        if (!this.socket) return;
        
        // Map all incoming Socket.IO events to our internal event system
        this.socket.onAny((event, ...args) => {
            console.log(`Socket Client: Received ${event} event from server`, args);
            this.emitEvent(event, args[0]);
        });
        
        // Special handling for narrative text
        this.socket.on('narrativeResponse', (data) => {
            if (data && data.text && this.textBuffer) {
                this.processTextFragment(data.text);
            }
        });
        
        // Special handling for introduction text
        this.socket.on('gameIntroduction', (data) => {
            if (data && data.introduction && this.textBuffer) {
                this.processTextFragment(data.introduction);
            }
            
            if (data && data.initialRoomDescription && this.textBuffer) {
                this.processTextFragment(data.initialRoomDescription);
            }
        });
    }
    
    /**
     * Process a text fragment by adding it to the TextBuffer
     * @param {string} text - Text fragment to process
     */
    processTextFragment(text) {
        if (!text) return;
        
        // Add text to the buffer if available
        if (this.textBuffer) {
            console.log(`Socket Client: Processing text fragment: "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"`);
            this.textBuffer.addText(text);
        } else {
            console.error('Socket Client: Text buffer not available');
            // Attempt to get text buffer again
            this.textBuffer = moduleRegistry.getModule('text-buffer');
            if (this.textBuffer) {
                this.textBuffer.addText(text);
            } else {
                // Emit a text event as fallback if no text buffer
                this.emitEvent('text', text);
            }
        }
    }
    
    /**
     * Attempt to reconnect to the server
     */
    attemptReconnect() {
        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
            console.error('Socket Client: Max reconnect attempts reached');
            return;
        }
        
        this.reconnectAttempts++;
        const delay = this.reconnectDelay * this.reconnectAttempts;
        
        console.log(`Socket Client: Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
        
        setTimeout(() => {
            if (!this.isConnected) {
                this.connect();
            }
        }, delay);
    }
    
    /**
     * Disconnect from the server
     */
    disconnect() {
        if (this.socket && this.isConnected) {
            this.socket.disconnect();
            this.isConnected = false;
        }
    }
    
    /**
     * Send a message to the server
     * @param {Object|string} data - Data to send
     * @returns {boolean} - Success status
     */
    send(data) {
        if (!this.isConnected || !this.socket) {
            console.error('Socket Client: Not connected');
            return false;
        }
        
        try {
            // For Socket.IO we send structured events
            if (typeof data === 'object') {
                const { type, ...restData } = data;
                if (type) {
                    // Use the type as the event name
                    this.socket.emit(type, restData);
                } else {
                    // Default to 'message' event
                    this.socket.emit('message', data);
                }
            } else {
                // Plain strings go to 'message' event
                this.socket.emit('message', { text: data });
            }
            return true;
        } catch (error) {
            console.error('Socket Client: Error sending message:', error);
            return false;
        }
    }

    /**
     * Send a command to the server
     * @param {string} command - The player's command
     * @returns {boolean} - Success status
     */
    sendCommand(command) {
        if (!this.isConnected || !this.socket) {
            console.error('Socket Client: Not connected, cannot send command');
            return false;
        }
        
        try {
            this.socket.emit('playerCommand', { command });
            return true;
        } catch (error) {
            console.error('Socket Client: Error sending command:', error);
            return false;
        }
    }

    /**
     * Request to start a new game
     * @returns {boolean} - Success status
     */
    requestStartGame() {
        if (!this.isConnected || !this.socket) {
            console.error('Socket Client: Not connected, cannot start game');
            return false;
        }
        
        try {
            this.socket.emit('startGame');
            return true;
        } catch (error) {
            console.error('Socket Client: Error starting game:', error);
            return false;
        }
    }

    /**
     * Request to save the current game state
     * @returns {boolean} - Success status
     */
    requestSaveGame() {
        if (!this.isConnected || !this.socket) {
            console.error('Socket Client: Not connected, cannot save game');
            return false;
        }
        
        try {
            this.socket.emit('saveGame');
            return true;
        } catch (error) {
            console.error('Socket Client: Error saving game:', error);
            return false;
        }
    }

    /**
     * Request to load a saved game state
     * @returns {boolean} - Success status
     */
    requestLoadGame() {
        if (!this.isConnected || !this.socket) {
            console.error('Socket Client: Not connected, cannot load game');
            return false;
        }
        
        try {
            this.socket.emit('loadGame');
            return true;
        } catch (error) {
            console.error('Socket Client: Error loading game:', error);
            return false;
        }
    }

    /**
     * Register an event handler
     * @param {string} event - Event name
     * @param {Function} callback - Event callback
     */
    on(event, callback) {
        if (!this.eventListeners[event]) {
            this.eventListeners[event] = [];
        }
        this.eventListeners[event].push(callback);
    }

    /**
     * Remove an event handler
     * @param {string} event - Event name
     * @param {Function} callback - Event callback to remove
     */
    off(event, callback) {
        if (!this.eventListeners[event]) return;
        
        if (callback) {
            // Remove specific callback
            this.eventListeners[event] = this.eventListeners[event].filter(cb => cb !== callback);
        } else {
            // Remove all callbacks for this event
            delete this.eventListeners[event];
        }
    }

    /**
     * Emit an event to all registered listeners
     * @param {string} event - Event name
     * @param {*} data - Event data
     */
    emitEvent(event, data) {
        if (!this.eventListeners[event]) return;
        
        for (const callback of this.eventListeners[event]) {
            try {
                callback(data);
            } catch (error) {
                console.error(`Socket Client: Error in '${event}' event handler:`, error);
            }
        }
    }

    /**
     * Check if the socket is connected
     * @returns {boolean} - Connection status
     */
    getConnectionStatus() {
        return this.isConnected;
    }
}

// Create the singleton instance
const SocketClient = new SocketClientModule();

// Register with the module registry
moduleRegistry.register(SocketClient);

// Export the module
export { SocketClient };

// Keep a reference in window for loader system
window.SocketClient = SocketClient;
