/**
 * Chrome Runtime API Emulation for Qt WebEngine
 *
 * This script emulates the chrome.runtime API for Chrome extensions
 * running in a Qt WebEngine-based browser. It uses QWebChannel to
 * communicate with the Python backend.
 */

(function() {
    'use strict';

    // Only initialize if we're in an extension context
    if (!window.location.href.startsWith('qextension://')) {
        return;
    }

    console.log('Initializing chrome.runtime API emulation for {window.location.href)');

    // Extract extension ID from URL
    const extensionId = window.location.hostname;
    
    // Create the chrome namespace if it doesn't exist
    window.chrome = window.chrome || {};
    
    // Create a runtime object with basic functionality
    window.chrome.runtime = window.chrome.runtime || {};
    
    // Store message listeners
    const messageListeners = [];
    
    // Store port connections
    const portConnections = {};
    
    // Store last error
    let lastError = null;

    // Create a QWebChannel connection to communicate with the Python backend
    let channel = null;
    let runtimeBridge = null;
    
    // Flag to track initialization status
    let isInitialized = false;
    
    // Flag to track if we're currently attempting initialization
    let isInitializing = false;
    
    // Queue for storing operations that need to be performed after initialization
    const pendingOperations = [];
    
    // Track initialization attempts
    let initAttempts = 0;
    const MAX_INIT_ATTEMPTS = 100;
    
    // Function to execute pending operations once initialized
    function executePendingOperations() {
        console.log(`Executing ${pendingOperations.length} pending operations`);
        while (pendingOperations.length > 0) {
            const operation = pendingOperations.shift();
            try {
                operation();
            } catch (e) {
                console.error('Error executing pending operation:', e);
            }
        }
    }

    // Create a dummy chrome.runtime object with empty methods to prevent errors
    function createDummyChromeRuntime() {
        return {
            id: extensionId,
            getURL: function(path) { return `qextension://${extensionId}/${path}`; },
            sendMessage: function() {
                console.warn('chrome.runtime not fully initialized yet, queueing sendMessage operation');
                const args = arguments;
                pendingOperations.push(() => {
                    window.chrome.runtime.sendMessage.apply(null, args);
                });
            },
            connect: function() {
                console.warn('chrome.runtime not fully initialized yet, returning dummy port');
                // Return a dummy port object that will queue operations
                const dummyPort = {
                    name: arguments[0]?.name || 'dummy-port',
                    postMessage: function(msg) {
                        console.warn('Port not ready, queueing postMessage operation');
                        const message = msg;
                        pendingOperations.push(() => {
                            const realPort = window.chrome.runtime.connect.apply(null, args);
                            realPort.postMessage(message);
                            return realPort;
                        });
                    },
                    onMessage: {
                        addListener: function(listener) {
                            pendingOperations.push(() => {
                                const realPort = window.chrome.runtime.connect.apply(null, args);
                                realPort.onMessage.addListener(listener);
                            });
                        },
                        removeListener: function() {},
                        hasListener: function() { return false; }
                    },
                    onDisconnect: {
                        addListener: function(listener) {
                            pendingOperations.push(() => {
                                const realPort = window.chrome.runtime.connect.apply(null, args);
                                realPort.onDisconnect.addListener(listener);
                            });
                        },
                        removeListener: function() {},
                        hasListener: function() { return false; }
                    },
                    disconnect: function() {}
                };
                const args = arguments;
                pendingOperations.push(() => {
                    return window.chrome.runtime.connect.apply(null, args);
                });
                return dummyPort;
            },
            getManifest: function() {
                return {
                    name: "Extension (Loading...)",
                    version: "0.0.0",
                    manifest_version: 3
                };
            },
            getBackgroundPage: function() {
                console.warn('chrome.runtime not fully initialized yet, queueing getBackgroundPage operation');
                const args = arguments;
                pendingOperations.push(() => {
                    window.chrome.runtime.getBackgroundPage.apply(null, args);
                });
            },
            onMessage: {
                addListener: function(callback) {
                    // Store the listener to be added once initialized
                    pendingOperations.push(() => {
                        window.chrome.runtime.onMessage.addListener(callback);
                    });
                },
                removeListener: function() {},
                hasListener: function() { return false; }
            },
            onConnect: {
                addListener: function(callback) {
                    // Store the listener to be added once initialized
                    pendingOperations.push(() => {
                        window.chrome.runtime.onConnect.addListener(callback);
                    });
                },
                removeListener: function() {},
                hasListener: function() { return false; }
            },
            // Add other event handlers
            onInstalled: {
                addListener: function() {},
                removeListener: function() {},
                hasListener: function() { return false; }
            },
            onStartup: {
                addListener: function() {},
                removeListener: function() {},
                hasListener: function() { return false; }
            },
            onSuspend: {
                addListener: function() {},
                removeListener: function() {},
                hasListener: function() { return false; }
            },
            onUpdateAvailable: {
                addListener: function() {},
                removeListener: function() {},
                hasListener: function() { return false; }
            },
            reload: function() {
                console.warn('chrome.runtime not fully initialized yet, queueing reload operation');
                pendingOperations.push(() => {
                    window.chrome.runtime.reload();
                });
            }
        };
    }
    
    // Set up a MutationObserver to detect when qt object might be injected
    function setupMutationObserver() {
        // Check if MutationObserver is supported
        if (typeof MutationObserver === 'undefined') {
            console.warn('MutationObserver not supported, falling back to polling');
            return;
        }
        
        console.log('Setting up MutationObserver to detect qt.webChannelTransport');
        
        // Function to check if transport is available
        function checkTransport() {
            if (typeof qt !== 'undefined' && qt.webChannelTransport) {
                console.log('MutationObserver: qt.webChannelTransport is available');
                return true;
            }
            return false;
        }
        
        // Create a MutationObserver to watch for changes to the DOM
        var observer = new MutationObserver(function(mutations) {
            if (checkTransport() && !isInitialized) {
                console.log('MutationObserver detected qt.webChannelTransport');
                attemptInitialization();
            }
        });
        
        // Start observing
        observer.observe(document, {
            childList: true,
            subtree: true,
            attributes: true
        });
        
        // Stop observing after 20 seconds to prevent memory leaks
        setTimeout(function() {
            observer.disconnect();
            if (!isInitialized) {
                console.warn('MutationObserver disconnected after timeout, qt.webChannelTransport not detected');
            }
        }, 20000);
    }
    
    // Initialize QWebChannel
    function initWebChannel() {
        // If we're already initializing, don't start another initialization process
        if (isInitializing) {
            console.log('QWebChannel initialization already in progress, skipping');
            return;
        }
        
        isInitializing = true;
        initAttempts++;
        
        console.log(`Starting QWebChannel initialization (attempt ${initAttempts}/${MAX_INIT_ATTEMPTS})`);
        
        // Create a dummy chrome.runtime object with empty methods to prevent errors
        // This will be replaced with the real implementation once QWebChannel is initialized
        if (!window.chrome) {
            window.chrome = {};
        }
        
        if (!window.chrome.runtime) {
            // Create a more robust dummy chrome.runtime object
            window.chrome.runtime = createDummyChromeRuntime();
        }
        
        // Set up a MutationObserver to detect when qt object might be injected
        setupMutationObserver();
        
        function attemptInitialization() {
            console.log(`Attempting to initialize QWebChannel, attempt #${initAttempts}/${MAX_INIT_ATTEMPTS}`);
            
            // Check if we've already initialized successfully
            if (isInitialized) {
                console.log('QWebChannel already initialized successfully, skipping');
                isInitializing = false;
                return;
            }
            
            // Check if we've exceeded the maximum number of attempts
            if (initAttempts > MAX_INIT_ATTEMPTS) {
                console.error(`Failed to initialize QWebChannel after ${MAX_INIT_ATTEMPTS} attempts. Chrome runtime API will not work.`);
                isInitializing = false;
                return;
            }
            
            // Check if QWebChannel is defined
            if (typeof QWebChannel === 'undefined') {
                console.error('QWebChannel not found. Waiting for qwebchannel.js to load...');
                
                // Try to load QWebChannel.js dynamically as a last resort
                if (initAttempts > 10 && initAttempts % 10 === 0) {
                    console.log('Attempting to dynamically load QWebChannel.js');
                    try {
                        // Notify the page that we need QWebChannel.js
                        document.dispatchEvent(new CustomEvent('requestQWebChannel', {
                            detail: { timestamp: Date.now() }
                        }));
                    } catch (e) {
                        console.error('Error dispatching requestQWebChannel event:', e);
                    }
                }
                
                // Schedule another attempt
                setTimeout(() => {
                    initAttempts++;
                    attemptInitialization();
                }, 200);
                return;
            }

            // Check if qt object is available
            if (typeof qt === 'undefined' || !qt.webChannelTransport) {
                console.log(`qt.webChannelTransport not available yet, retrying in 200ms... (attempt ${initAttempts}/${MAX_INIT_ATTEMPTS})`);
                
                // Try to request the transport object if it's not available after several attempts
                if (initAttempts % 5 === 0) {
                    console.log('Attempting to request qt.webChannelTransport');
                    
                    try {
                        // Notify the page that we need the transport object
                        document.dispatchEvent(new CustomEvent('requestWebChannelTransport', {
                            detail: { timestamp: Date.now() }
                        }));
                        
                        // Also check if window.__qtWebChannelTransportReady is set by test.py
                        if (window.__qtWebChannelTransportReady === true) {
                            console.log('window.__qtWebChannelTransportReady is true, but qt.webChannelTransport is not available');
                        }
                    } catch (e) {
                        console.error('Error dispatching requestWebChannelTransport event:', e);
                    }
                }
                
                // Schedule another attempt
                setTimeout(() => {
                    initAttempts++;
                    attemptInitialization();
                }, 200);
                return;
            }

            try {
                console.log('qt.webChannelTransport is available, creating QWebChannel');
                
                // Create a new QWebChannel using the qt.webChannelTransport object
                new QWebChannel(qt.webChannelTransport, function(ch) {
                    channel = ch;
                    
                    // Get the runtime bridge object exposed by the Python backend
                    runtimeBridge = channel.objects.runtimeBridge;
                    
                    if (!runtimeBridge) {
                        console.error('RuntimeBridge object not found in QWebChannel');
                        isInitializing = false;
                        
                        // Schedule another attempt
                        setTimeout(() => {
                            initAttempts++;
                            attemptInitialization();
                        }, 500);
                        return;
                    }
                    
                    console.log('RuntimeBridge object found, connecting signals');
                    
                    // Connect to the messageReceived signal
                    runtimeBridge.messageReceived.connect(function(message) {
                        onMessageReceived(JSON.parse(message));
                    });
                    
                    // Connect to the portMessageReceived signal
                    runtimeBridge.portMessageReceived.connect(function(portName, message) {
                        onPortMessageReceived(portName, JSON.parse(message));
                    });
                    
                    // Notify the backend that we're ready
                    runtimeBridge.extensionLoaded(extensionId, window.location.pathname);
                    
                    // Set initialization flag and execute any pending operations
                    isInitialized = true;
                    isInitializing = false;
                    console.log('Chrome runtime API emulation initialized for extension:', extensionId);
                    
                    // Set the global flag that test.py checks
                    window.__qtWebChannelTransportReady = true;
                    
                    // Execute any operations that were queued while waiting for initialization
                    executePendingOperations();
                    
                    // Dispatch an event to notify the page that chrome.runtime is ready
                    document.dispatchEvent(new CustomEvent('chrome.runtime.initialized', {
                        detail: { extensionId: extensionId }
                    }));
                    
                    // Also dispatch webChannelReady event for backward compatibility
                    document.dispatchEvent(new CustomEvent('webChannelReady', {
                        detail: { channel: channel }
                    }));
                });
            } catch (e) {
                console.error('Error initializing QWebChannel:', e);
                
                // Reset the initializing flag so we can try again
                isInitializing = false;
                
                // Schedule another attempt
                setTimeout(() => {
                    initAttempts++;
                    attemptInitialization();
                }, 300);
            }
        }
        
        // Start the initialization process
        attemptInitialization();
    }

    // Handle incoming messages from the backend
    function onMessageReceived(message) {
        // Notify all message listeners
        messageListeners.forEach(listener => {
            try {
                listener(message, { id: extensionId }, function(response) {
                    if (runtimeBridge) {
                        runtimeBridge.sendResponse(extensionId, JSON.stringify(response));
                    }
                });
            } catch (e) {
                console.error('Error in message listener:', e);
            }
        });
    }

    // Handle incoming port messages from the backend
    function onPortMessageReceived(portName, message) {
        const port = portConnections[portName];
        if (port && port.onMessage.listeners) {
            port.onMessage.listeners.forEach(listener => {
                try {
                    listener(message);
                } catch (e) {
                    console.error('Error in port message listener:', e);
                }
            });
        }
    }

    // Create an EventTarget-like object for event handling
    function createEventTarget() {
        const listeners = [];
        
        return {
            addListener: function(callback) {
                if (typeof callback === 'function' && !listeners.includes(callback)) {
                    listeners.push(callback);
                }
            },
            removeListener: function(callback) {
                const index = listeners.indexOf(callback);
                if (index !== -1) {
                    listeners.splice(index, 1);
                }
            },
            hasListener: function(callback) {
                return listeners.includes(callback);
            },
            listeners: listeners
        };
    }

    // Implement chrome.runtime.id
    Object.defineProperty(window.chrome.runtime, 'id', {
        get: function() {
            return extensionId;
        },
        enumerable: true
    });

    // Implement chrome.runtime.lastError
    Object.defineProperty(window.chrome.runtime, 'lastError', {
        get: function() {
            const error = lastError;
            lastError = null; // Clear after reading
            return error ? { message: error } : undefined;
        },
        enumerable: true
    });

    // Implement chrome.runtime.getURL
    window.chrome.runtime.getURL = function(path) {
        if (path.startsWith('/')) {
            path = path.substring(1);
        }
        return `qextension://${extensionId}/${path}`;
    };

    // Implement chrome.runtime.getManifest
    window.chrome.runtime.getManifest = function() {
        // If not initialized, return a basic manifest
        if (!isInitialized) {
            console.warn('chrome.runtime not fully initialized yet, returning empty manifest');
            return {
                name: "Extension (Loading...)",
                version: "0.0.0",
                manifest_version: 3
            };
        }
        
        // We'll need to fetch this from the backend
        if (runtimeBridge) {
            // This is a synchronous call in Chrome, but we need to make it async
            // For now, return a basic manifest and update it later
            const manifestStr = runtimeBridge.getManifest(extensionId);
            try {
                return JSON.parse(manifestStr);
            } catch (e) {
                console.error('Error parsing manifest:', e);
                return {};
            }
        }
        return {};
    };

    // Implement chrome.runtime.sendMessage
    window.chrome.runtime.sendMessage = function(extensionIdOrMessage, messageOrCallback, optionsOrCallback, callback) {
        let targetExtensionId = extensionId;
        let message;
        let options = {};
        let responseCallback;

        // Parse arguments based on their types
        if (typeof extensionIdOrMessage === 'string') {
            targetExtensionId = extensionIdOrMessage;
            message = messageOrCallback;
            
            if (typeof optionsOrCallback === 'function') {
                responseCallback = optionsOrCallback;
            } else {
                options = optionsOrCallback || {};
                responseCallback = callback;
            }
        } else {
            message = extensionIdOrMessage;
            
            if (typeof messageOrCallback === 'function') {
                responseCallback = messageOrCallback;
            } else {
                options = messageOrCallback || {};
                responseCallback = optionsOrCallback;
            }
        }

        // If not initialized, queue the operation
        if (!isInitialized) {
            console.warn('chrome.runtime not fully initialized yet, queueing sendMessage operation');
            const args = arguments;
            pendingOperations.push(() => {
                window.chrome.runtime.sendMessage.apply(null, args);
            });
            return;
        }

        // Send the message through the bridge
        if (runtimeBridge) {
            runtimeBridge.sendMessage(
                targetExtensionId,
                JSON.stringify(message),
                JSON.stringify(options),
                function(response) {
                    if (responseCallback) {
                        try {
                            responseCallback(JSON.parse(response));
                        } catch (e) {
                            console.error('Error in sendMessage callback:', e);
                            lastError = e.message;
                            responseCallback();
                        }
                    }
                }
            );
        } else if (responseCallback) {
            lastError = 'Runtime bridge not available';
            responseCallback();
        }
    };

    // Implement chrome.runtime.onMessage
    window.chrome.runtime.onMessage = createEventTarget();
    
    // Add the listeners to our internal array for dispatching
    window.chrome.runtime.onMessage.addListener = function(callback) {
        if (typeof callback === 'function') {
            // If not initialized, queue the operation
            if (!isInitialized) {
                console.warn('chrome.runtime not fully initialized yet, queueing onMessage.addListener operation');
                pendingOperations.push(() => {
                    window.chrome.runtime.onMessage.addListener(callback);
                });
                return;
            }
            
            // Add to our internal array if not already there
            if (!messageListeners.includes(callback)) {
                messageListeners.push(callback);
            }
        }
        // Call the original method
        return createEventTarget().addListener.call(this, callback);
    };

    // Implement chrome.runtime.connect
    window.chrome.runtime.connect = function(extensionIdOrOptions, options) {
        let targetExtensionId = extensionId;
        let connectOptions = {};
        
        if (typeof extensionIdOrOptions === 'string') {
            targetExtensionId = extensionIdOrOptions;
            connectOptions = options || {};
        } else {
            connectOptions = extensionIdOrOptions || {};
        }
        
        const portName = connectOptions.name || '';
        
        // If not initialized, queue the operation and return a dummy port
        if (!isInitialized) {
            console.warn('chrome.runtime not fully initialized yet, returning dummy port');
            
            // Create a dummy port that will queue operations
            const dummyPort = {
                name: portName,
                disconnect: function() {
                    console.log('Dummy port disconnected');
                },
                postMessage: function(message) {
                    console.warn('Port not ready, queueing postMessage operation');
                    const msg = message;
                    const args = arguments;
                    pendingOperations.push(() => {
                        const realPort = window.chrome.runtime.connect(connectOptions);
                        realPort.postMessage(msg);
                    });
                },
                onDisconnect: createEventTarget(),
                onMessage: createEventTarget()
            };
            
            // Queue the actual connect operation for when we're initialized
            const connectArgs = arguments;
            pendingOperations.push(() => {
                const realPort = window.chrome.runtime.connect.apply(null, connectArgs);
                // Transfer any listeners from dummy port to real port
                dummyPort.onMessage.listeners.forEach(listener => {
                    realPort.onMessage.addListener(listener);
                });
                dummyPort.onDisconnect.listeners.forEach(listener => {
                    realPort.onDisconnect.addListener(listener);
                });
                return realPort;
            });
            
            return dummyPort;
        }
        
        // Create the real port
        const port = {
            name: portName,
            disconnect: function() {
                if (runtimeBridge) {
                    runtimeBridge.disconnectPort(targetExtensionId, portName);
                }
                delete portConnections[portName];
            },
            postMessage: function(message) {
                if (runtimeBridge) {
                    runtimeBridge.postPortMessage(targetExtensionId, portName, JSON.stringify(message));
                }
            },
            onDisconnect: createEventTarget(),
            onMessage: createEventTarget()
        };
        
        // Store the port connection
        portConnections[portName] = port;
        
        // Notify the backend about the connection
        if (runtimeBridge) {
            runtimeBridge.connectPort(targetExtensionId, portName);
        }
        
        return port;
    };

    // Implement chrome.runtime.onConnect
    window.chrome.runtime.onConnect = createEventTarget();

    // Implement chrome.runtime.onInstalled
    window.chrome.runtime.onInstalled = createEventTarget();

    // Implement chrome.runtime.onStartup
    window.chrome.runtime.onStartup = createEventTarget();

    // Implement chrome.runtime.onSuspend
    window.chrome.runtime.onSuspend = createEventTarget();

    // Implement chrome.runtime.getBackgroundPage
    window.chrome.runtime.getBackgroundPage = function(callback) {
        // If not initialized, queue the operation
        if (!isInitialized) {
            console.warn('chrome.runtime not fully initialized yet, queueing getBackgroundPage operation');
            const args = arguments;
            pendingOperations.push(() => {
                window.chrome.runtime.getBackgroundPage.apply(null, args);
            });
            return;
        }
        
        if (runtimeBridge) {
            const success = runtimeBridge.getBackgroundPage(extensionId);
            if (success) {
                // In a real implementation, we would return the actual background page
                // For now, we'll just simulate success with a mock object
                if (callback) {
                    const mockBackgroundPage = {
                        // Add any properties or methods that the background page would have
                        // This is just a simple mock for testing
                        console: console,
                        chrome: chrome,
                        document: {
                            title: "Background Page"
                        },
                        // Add a test method that extensions might use
                        sendTestMessage: function(message) {
                            console.log("Background page received test message:", message);
                            return "Response from background page";
                        }
                    };
                    
                    // Call the callback with the mock background page
                    setTimeout(() => {
                        callback(mockBackgroundPage);
                    }, 50);
                }
            } else {
                lastError = "Could not access background page";
                if (callback) {
                    setTimeout(() => {
                        callback(null);
                    }, 50);
                }
            }
        } else {
            lastError = "Runtime bridge not available";
            if (callback) {
                setTimeout(() => {
                    callback(null);
                }, 50);
            }
        }
    };

    // Implement chrome.runtime.onUpdateAvailable
    window.chrome.runtime.onUpdateAvailable = createEventTarget();

    // Implement chrome.runtime.reload
    window.chrome.runtime.reload = function() {
        if (runtimeBridge) {
            runtimeBridge.reloadExtension(extensionId);
        }
    };

    // Helper function to check if chrome.runtime API is initialized
    window.isChromeRuntimeInitialized = function() {
        return isInitialized;
    };
    
    // Helper function to check if transport is available
    function checkTransport() {
        if (typeof qt !== 'undefined' && qt.webChannelTransport) {
            console.log('Transport check: qt.webChannelTransport is available');
            return true;
        }
        return false;
    }
    
    // Add a listener for when the document is ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function() {
            console.log('DOMContentLoaded event fired, initializing chrome.runtime API');
            initWebChannel();
            
            // Add a listener for the window load event as well
            window.addEventListener('load', function() {
                console.log('Window load event fired, checking chrome.runtime API initialization');
                if (!isInitialized) {
                    console.warn('chrome.runtime API still not initialized after window load, retrying...');
                    initWebChannel();
                }
            });
        });
    } else {
        console.log('Document already loaded, initializing chrome.runtime API immediately');
        initWebChannel();
    }
    
    // Add a listener for the requestWebChannelTransport event
    document.addEventListener('requestWebChannelTransport', function(event) {
        console.log('Received requestWebChannelTransport event');
        // This event is dispatched by the BrowserTab class in test.py
        // when it detects that the page needs the transport object
        if (checkTransport()) {
            console.log('qt.webChannelTransport is available, initializing QWebChannel');
            initWebChannel();
        } else {
            console.warn('qt.webChannelTransport still not available after request');
            
            // Try again after a short delay
            setTimeout(function() {
                if (checkTransport()) {
                    console.log('qt.webChannelTransport became available after delay, initializing QWebChannel');
                    initWebChannel();
                }
            }, 500);
        }
    });
    
    // Add a listener for the webChannelReady event
    document.addEventListener('webChannelReady', function(event) {
        console.log('Received webChannelReady event');
        if (!isInitialized) {
            console.log('webChannelReady event received, but chrome.runtime not initialized, attempting initialization');
            attemptInitialization();
        }
    });
    
    // Add a listener for the webChannelTransportRequested event
    document.addEventListener('webChannelTransportRequested', function(event) {
        console.log('Received webChannelTransportRequested event');
        // This event is dispatched when another script needs the transport object
        if (checkTransport()) {
            console.log('qt.webChannelTransport is available, notifying requesters');
            document.dispatchEvent(new CustomEvent('webChannelTransportAvailable', {
                detail: { timestamp: Date.now() }
            }));
        }
    });
    
    // Add a listener for the requestQWebChannel event
    document.addEventListener('requestQWebChannel', function(event) {
        console.log('Received requestQWebChannel event');
        // This event is dispatched when QWebChannel.js needs to be loaded
        if (typeof QWebChannel !== 'undefined') {
            console.log('QWebChannel is already available');
            document.dispatchEvent(new CustomEvent('QWebChannelAvailable', {
                detail: { timestamp: Date.now() }
            }));
        }
    });
})();
