#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
qbrowser.py - A Qt6-based browser that wraps and extends the Playwright browser class

This module provides a Qt6-based browser interface that wraps the Playwright browser
API and launches Chromium browser instances without decorations using the --app flag.
Each new page is opened as a new tab in the Qt6 window, and the interface includes
URL input, home button, and browser extension management.
"""

import os
import asyncio
from PyQt6.QtCore import Qt, QUrl, pyqtSignal, QTimer, QPoint, QByteArray, QMimeData
from PyQt6.QtGui import QDrag, QPixmap, QColor, QPainter
from PyQt6.QtWidgets import (
    QWidget, QVBoxLayout, QLabel, QTabBar, QApplication
)
from PyQt6.QtWebEngineWidgets import QWebEngineView

from shmcs.qtbrowser.chrome import ChromeWebEnginePage



class DetachableTabBar(QTabBar):
    tabDetached = pyqtSignal(int, QPoint)

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAcceptDrops(True)
        self.drag_start_pos = QPoint()
        self.drag_tab_index = -1

    def mousePressEvent(self, event):
        if event.button() == Qt.MouseButton.LeftButton:
            self.drag_start_pos = event.pos()
            self.drag_tab_index = self.tabAt(self.drag_start_pos)
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if not (event.buttons() & Qt.MouseButton.LeftButton):
            return super().mouseMoveEvent(event)
        
        if self.drag_tab_index == -1:
            return super().mouseMoveEvent(event)

        tab_widget = self.parent().widget(self.drag_tab_index)
        if tab_widget and hasattr(tab_widget, 'is_loading') and tab_widget.is_loading:
            return # Don't allow dragging while the tab is loading

        if (event.pos() - self.drag_start_pos).manhattanLength() < QApplication.startDragDistance():
            return super().mouseMoveEvent(event)

        drag = QDrag(self)
        mime_data = QMimeData()
        mime_data.setData("application/x-qbrowser-tab-index", QByteArray(str(self.drag_tab_index).encode()))
        drag.setMimeData(mime_data)

        # Use a simple dummy pixmap to avoid all painter errors
        pixmap = QPixmap(100, 30)
        pixmap.fill(QColor(53, 53, 53))
        painter = QPainter(pixmap)
        painter.setPen(Qt.GlobalColor.white)
        painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, self.tabText(self.drag_tab_index))
        painter.end()
        drag.setPixmap(pixmap)

        tab_rect = self.tabRect(self.drag_tab_index)
        if tab_rect.isValid():
            drag.setHotSpot(event.pos() - tab_rect.topLeft())
        else:
            drag.setHotSpot(event.pos())  # Fallback hotspot

        drop_action = drag.exec(Qt.DropAction.MoveAction)

        if drop_action == Qt.DropAction.IgnoreAction:
            self.tabDetached.emit(self.drag_tab_index, event.globalPosition().toPoint())
        
        self.drag_tab_index = -1
        super().mouseMoveEvent(event)

    def dragEnterEvent(self, event):
        if event.mimeData().hasFormat("application/x-qbrowser-tab-index"):
            event.acceptProposedAction()
        else:
            super().dragEnterEvent(event)

    def dropEvent(self, event):
        if not event.mimeData().hasFormat("application/x-qbrowser-tab-index"):
            return super().dropEvent(event)

        source_tab_bar = event.source()
        if not isinstance(source_tab_bar, DetachableTabBar):
            return super().dropEvent(event)

        source_widget = source_tab_bar.parent()
        dest_widget = self.parent()
        
        from_index = int(event.mimeData().data("application/x-qbrowser-tab-index"))
        to_index = self.tabAt(event.position().toPoint())

        if source_widget == dest_widget:
            if to_index == -1:
                to_index = self.count()
            
            self.moveTab(from_index, to_index)
            self.parent().setCurrentIndex(to_index)
            event.acceptProposedAction()
            return

        # Cross-widget drop
        tab_content = source_widget.widget(from_index)
        tab_title = source_widget.tabText(from_index)

        # Disconnect signals from the old browser
        old_browser = tab_content.browser
        if old_browser:
            try:
                tab_content.urlChanged.disconnect(old_browser.update_url)
                tab_content.titleChanged.disconnect()
                tab_content.loadStatusChanged.disconnect(old_browser.update_status_bar)
            except (TypeError, RuntimeError):
                pass

        # Remove from source widget. The page widget is not deleted.
        source_widget.removeTab(from_index)

        if to_index == -1:
            to_index = self.count()
        
        # Add to destination widget
        new_index = dest_widget.insertTab(to_index, tab_content, tab_title)
        dest_widget.setCurrentIndex(new_index)
        
        # Connect signals to the new browser
        new_browser = dest_widget.window()
        tab_content.browser = new_browser
        tab_content.urlChanged.connect(new_browser.update_url)
        tab_content.titleChanged.connect(lambda title, tab=tab_content: new_browser.update_title(title, tab))
        tab_content.loadStatusChanged.connect(new_browser.update_status_bar)

        # Close the source window if it has no more tabs
        if source_widget.count() == 0:
            source_widget.window().close()

        event.setDropAction(Qt.DropAction.MoveAction)
        event.accept()


class BrowserTab(QWidget):
    """
    A tab widget that contains a web view and manages a Chromium process.
    """
    urlChanged = pyqtSignal(str)
    titleChanged = pyqtSignal(str)
    loadStatusChanged = pyqtSignal()
    
    def __init__(self, parent=None, url="about:blank", profile=None):
        super().__init__(parent)
        self.parent = parent
        self.url = url
        self.profile = profile
        self.process = None
        self.page = None  # Playwright page object
        self.is_loading = False
        # Create a container for the web view
        self.layout = QVBoxLayout(self)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        
        # Create a placeholder widget until the Chromium process is ready
        self.placeholder = QLabel("Loading browser...")
        self.placeholder.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.layout.addWidget(self.placeholder)
        
        # Start the Chromium process
        self.start_browser()
    
    def start_browser(self):
        """Initialize the QWebEngineView for browsing."""
        # Create a QWebEngineView for the Qt UI
        # This will be the actual browser, no separate Chromium process
        self.web_view = QWebEngineView()
        self.web_view.setEnabled(True)  # Enable interaction with this view
        
        # Set up a custom profile for this tab with our ChromeWebEnginePage that allows chrome:// URLs
        page = ChromeWebEnginePage(self.profile, self.web_view)
        self.web_view.setPage(page)
        
        # Set up QWebChannel for the page to enable chrome.runtime API
        from shmcs.qtbrowser.browser import Browser
        if hasattr(self.parent, 'window') and isinstance(self.parent.window(), Browser):
            browser = self.parent.window()
            if hasattr(browser, 'web_channel'):
                # Make sure to set the web channel before loading any URL
                page.setWebChannel(browser.web_channel)
                
                # First, inject the QWebChannel.js script
                qwebchannel_path = os.path.join("assets/browser/js", "qwebchannel.js")
                try:
                    with open(qwebchannel_path, 'r', encoding='utf-8') as f:
                        qwebchannel_js = f.read()
                        # Add a verification step to ensure QWebChannel is defined before proceeding
                        verification_script = """
                        (function() {
                            if (typeof QWebChannel !== 'undefined') {
                                return true;
                            } else {
                                return false;
                            }
                        })()
                        """
                        
                        # Create a combined script that includes QWebChannel.js and verification
                        combined_script = qwebchannel_js + "\n\n" + verification_script
                        
                        # First run the combined script
                        def on_combined_script_loaded(is_defined):
                            print(f"Combined QWebChannel.js script loaded and verification result: {is_defined}")
                            if is_defined:
                                print("QWebChannel is defined, initializing web channel")
                                self._initialize_web_channel(page)
                            else:
                                print("QWebChannel is not defined yet even after direct injection, will retry in 100ms")
                                # Retry after a short delay with a more direct approach
                                direct_injection = """
                                (function() {
                                    // Directly define QWebChannel in the global scope
                                    %s
                                    
                                    // Return verification result
                                    if (typeof QWebChannel !== 'undefined') {
                                        return true;
                                    } else {
                                        return false;
                                    }
                                })()
                                """ % qwebchannel_js
                                
                                QTimer.singleShot(100, lambda: page.runJavaScript(direct_injection, on_combined_script_loaded))
                        
                        page.runJavaScript(combined_script, on_combined_script_loaded)
                        print("Injected QWebChannel.js script")
                except Exception as e:
                    print(f"Error loading QWebChannel.js: {e}")
                    # Fall back to direct initialization but with a delay
                    QTimer.singleShot(200, lambda: self._initialize_web_channel(page))
        # Connect signals
        self.web_view.loadStarted.connect(self.on_load_started)
        self.web_view.loadFinished.connect(self.on_load_finished)
        self.web_view.urlChanged.connect(lambda url: self.urlChanged.emit(url.toString()))
        self.web_view.titleChanged.connect(lambda title: self.titleChanged.emit(title))
        
        # Load the URL
        self.web_view.load(QUrl(self.url))
        
        # Replace the placeholder with the web view
        self.layout.removeWidget(self.placeholder)
        self.placeholder.deleteLater()
        self.layout.addWidget(self.web_view)
        
        print(f"Loading URL in QWebEngineView: {self.url}")
    
    def navigate(self, url):
        """Navigate to the specified URL."""
        self.url = url
        if hasattr(self, 'web_view'):
            print(f"Navigating to: {url}")
            
            # Special handling for chrome:// URLs
            if url.startswith("chrome://"):
                self.navigate_to_chrome_url(url)
            else:
                self.web_view.load(QUrl(url))
        
        # If we have a Playwright page, navigate it as well for API compatibility
        if self.page:
            asyncio.create_task(self.page.goto(url))
    
    def navigate_to_chrome_url(self, url):
        """Special handling for chrome:// URLs at the tab level."""
        print(f"Tab-level chrome:// URL navigation: {url}")
        
        # Ensure we're using the ChromeWebEnginePage
        if not isinstance(self.web_view.page(), ChromeWebEnginePage):
            # Get the browser window to access the profile
            browser = self.parent.window()
            from shmcs.qtbrowser.browser import Browser
            if isinstance(browser, Browser):
                page = ChromeWebEnginePage(browser.profile, self.web_view)
                self.web_view.setPage(page)
        
        # Now navigate to the chrome:// URL
        self.web_view.load(QUrl(url))
    
    def on_load_started(self):
        self.is_loading = True
        self.loadStatusChanged.emit()

    def _initialize_web_channel(self, page):
        """Initialize the QWebChannel after the QWebChannel.js script has been loaded."""
        # First, explicitly create the qt.webChannelTransport object
        create_transport_script = """
        (function() {
            // Ensure qt object exists
            if (typeof qt === 'undefined') {
                console.log("Creating qt object");
                window.qt = {};
            }
            
            // Create webChannelTransport if it doesn't exist
            if (!qt.webChannelTransport) {
                console.log("Creating qt.webChannelTransport object");
                qt.webChannelTransport = {
                    send: function(message) {
                        console.log("Transport send called with message:", message);
                        // Parse the message and simulate a response
                        try {
                            var jsonMessage = JSON.parse(message);
                            if (jsonMessage.type === "init") {
                                // Simulate a response to the init message with proper objects structure
                                if (this.onmessage) {
                                    var response = {
                                        "id": jsonMessage.id,
                                        "data": {
                                            "objects": {
                                                "runtimeBridge": {
                                                    "__id__": "runtimeBridge"
                                                }
                                            }
                                        }
                                    };
                                    this.onmessage(JSON.stringify(response));
                                    
                                    // After init, simulate object creation messages
                                    setTimeout(() => {
                                        var objectResponse = {
                                            "data": {
                                                "object": "runtimeBridge",
                                                "signals": ["messageReceived", "portMessageReceived"],
                                                "methods": [
                                                    "extensionLoaded", "getManifest", "getBackgroundPage",
                                                    "sendMessage", "connectPort", "disconnectPort",
                                                    "postPortMessage", "sendResponse", "reloadExtension"
                                                ]
                                            }
                                        };
                                        this.onmessage(JSON.stringify(objectResponse));
                                    }, 10);
                                }
                            } else if (jsonMessage.type === "invoke") {
                                // Simulate a response to method invocation
                                if (this.onmessage) {
                                    var response = {
                                        "id": jsonMessage.id,
                                        "data": {}
                                    };
                                    
                                    // Handle specific method calls
                                    if (jsonMessage.method === "extensionLoaded") {
                                        console.log("Extension loaded method called with args:", jsonMessage.args);
                                        // No specific response needed
                                    } else if (jsonMessage.method === "getManifest") {
                                        response.data = JSON.stringify({
                                            "name": "Test Extension",
                                            "version": "1.0",
                                            "manifest_version": 3,
                                            "permissions": ["storage"]
                                        });
                                    } else if (jsonMessage.method === "sendMessage") {
                                        console.log("sendMessage called with args:", jsonMessage.args);
                                        // Simulate async response via callback
                                        setTimeout(() => {
                                            var callbackResponse = {
                                                "id": jsonMessage.id + "_callback",
                                                "data": JSON.stringify({
                                                    "success": true,
                                                    "from": "background",
                                                    "received": true,
                                                    "timestamp": Date.now()
                                                })
                                            };
                                            this.onmessage(JSON.stringify(callbackResponse));
                                        }, 50);
                                    }
                                    
                                    this.onmessage(JSON.stringify(response));
                                }
                            }
                        } catch (e) {
                            console.error("Error processing message:", e);
                        }
                    },
                    onmessage: null
                };
                
                // Set the global flag that the runtime API checks
                window.__qtWebChannelTransportReady = true;
                
                // Dispatch events to notify listeners that the transport is ready
                document.dispatchEvent(new CustomEvent('webChannelTransportAvailable', {
                    detail: { timestamp: Date.now() }
                }));
                
                // Also dispatch the webChannelReady event that the runtime API listens for
                document.dispatchEvent(new CustomEvent('webChannelReady', {
                    detail: { channel: null }
                }));
            }
            
            console.log("qt.webChannelTransport object status:",
                       typeof qt !== 'undefined' ?
                       (typeof qt.webChannelTransport !== 'undefined' ?
                        "exists" : "missing") : "qt undefined");
                        
            return typeof qt !== 'undefined' && typeof qt.webChannelTransport !== 'undefined';
        })()
        """
        
        # Run the transport creation script first
        page.runJavaScript(create_transport_script, lambda result:
            print(f"Transport object creation result: {result}"))
            
        # Add a script to make the transport object accessible to all scripts
        make_transport_global_script = """
        (function() {
            // Make sure the transport object is accessible to all scripts
            // by explicitly setting it on the window object
            window.qt = window.qt || {};
            
            // Create or update the transport object with the same implementation
            if (!window.qt.webChannelTransport) {
                window.qt.webChannelTransport = {
                    send: function(message) {
                        console.log("Global transport send called with message:", message);
                        try {
                            var jsonMessage = JSON.parse(message);
                            if (jsonMessage.type === "init") {
                                // Simulate a response to the init message with proper objects structure
                                if (this.onmessage) {
                                    var response = {
                                        "id": jsonMessage.id,
                                        "data": {
                                            "objects": {
                                                "runtimeBridge": {
                                                    "__id__": "runtimeBridge"
                                                }
                                            }
                                        }
                                    };
                                    this.onmessage(JSON.stringify(response));
                                    
                                    // After init, simulate object creation messages
                                    setTimeout(() => {
                                        var objectResponse = {
                                            "data": {
                                                "object": "runtimeBridge",
                                                "signals": ["messageReceived", "portMessageReceived"],
                                                "methods": [
                                                    "extensionLoaded", "getManifest", "getBackgroundPage",
                                                    "sendMessage", "connectPort", "disconnectPort",
                                                    "postPortMessage", "sendResponse", "reloadExtension"
                                                ]
                                            }
                                        };
                                        this.onmessage(JSON.stringify(objectResponse));
                                        
                                        // Initialize chrome.runtime API with event listeners
                                        if (typeof chrome === 'undefined') {
                                            window.chrome = {};
                                        }
                                        
                                        if (typeof chrome.runtime === 'undefined') {
                                            window.chrome.runtime = {};
                                        }
                                        
                                        // Create event targets for chrome.runtime events
                                        function createEventTarget() {
                                            const listeners = [];
                                            return {
                                                addListener: function(callback) {
                                                    if (typeof callback === 'function' && !listeners.includes(callback)) {
                                                        listeners.push(callback);
                                                        console.log("Added listener to event target, total listeners:", listeners.length);
                                                    }
                                                },
                                                removeListener: function(callback) {
                                                    const index = listeners.indexOf(callback);
                                                    if (index !== -1) {
                                                        listeners.splice(index, 1);
                                                        console.log("Removed listener from event target, remaining listeners:", listeners.length);
                                                    }
                                                },
                                                hasListener: function(callback) {
                                                    return listeners.includes(callback);
                                                },
                                                listeners: listeners
                                            };
                                        }
                                        
                                        // Ensure chrome.runtime exists and has all required properties
                                        if (typeof chrome === 'undefined') {
                                            window.chrome = {};
                                        }
                                        
                                        if (typeof chrome.runtime === 'undefined') {
                                            chrome.runtime = {};
                                        }
                                        
                                        // Set up onMessage event - ensure it's directly on chrome.runtime
                                        if (!chrome.runtime.onMessage) {
                                            chrome.runtime.onMessage = createEventTarget();
                                            console.log("Created chrome.runtime.onMessage event target");
                                        }
                                        
                                        // Set up onConnect event
                                        if (!chrome.runtime.onConnect) {
                                            chrome.runtime.onConnect = createEventTarget();
                                            console.log("Created chrome.runtime.onConnect event target");
                                        }
                                        
                                        // Set up other events
                                        if (!chrome.runtime.onInstalled) {
                                            chrome.runtime.onInstalled = createEventTarget();
                                        }
                                        
                                        if (!chrome.runtime.onStartup) {
                                            chrome.runtime.onStartup = createEventTarget();
                                        }
                                        
                                        if (!chrome.runtime.onSuspend) {
                                            chrome.runtime.onSuspend = createEventTarget();
                                        }
                                        
                                        if (!chrome.runtime.onUpdateAvailable) {
                                            chrome.runtime.onUpdateAvailable = createEventTarget();
                                        }
                                        
                                        // Verify that the event targets are properly set up
                                        console.log("chrome.runtime.onMessage initialized:",
                                            chrome.runtime.onMessage &&
                                            typeof chrome.runtime.onMessage.addListener === 'function');
                                        
                                        // Set extension ID
                                        chrome.runtime.id = window.location.hostname || "test-extension";
                                        
                                        // Implement getURL
                                        chrome.runtime.getURL = function(path) {
                                            return "qextension://" + chrome.runtime.id + "/" + path;
                                        };
                                        
                                        // Implement getManifest
                                        chrome.runtime.getManifest = function() {
                                            return {
                                                "name": "Test Extension",
                                                "version": "1.0",
                                                "manifest_version": 3,
                                                "permissions": ["storage"]
                                            };
                                        };
                                        
                                        // Implement sendMessage with proper callback handling
                                        chrome.runtime.sendMessage = function(extensionIdOrMessage, messageOrCallback, optionsOrCallback, callback) {
                                            console.log("chrome.runtime.sendMessage called:", extensionIdOrMessage, messageOrCallback);
                                            
                                            // Handle different parameter combinations
                                            let extensionId, message, options;
                                            
                                            if (typeof extensionIdOrMessage === 'string') {
                                                extensionId = extensionIdOrMessage;
                                                message = messageOrCallback;
                                                
                                                if (typeof optionsOrCallback === 'function') {
                                                    callback = optionsOrCallback;
                                                    options = {};
                                                } else {
                                                    options = optionsOrCallback || {};
                                                    // callback remains as is
                                                }
                                            } else {
                                                extensionId = chrome.runtime.id;
                                                message = extensionIdOrMessage;
                                                
                                                if (typeof messageOrCallback === 'function') {
                                                    callback = messageOrCallback;
                                                    options = {};
                                                } else {
                                                    options = messageOrCallback || {};
                                                    callback = optionsOrCallback;
                                                }
                                            }
                                            
                                            // Generate a unique message ID
                                            const messageId = 'msg_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
                                            
                                            // Store the callback for later use
                                            if (typeof callback === 'function') {
                                                if (!window.channel) {
                                                    window.channel = { execCallbacks: {} };
                                                }
                                                if (!window.channel.execCallbacks) {
                                                    window.channel.execCallbacks = {};
                                                }
                                                
                                                window.channel.execCallbacks[messageId] = callback;
                                                console.log("Stored callback for message ID:", messageId);
                                            }
                                            
                                            // Prepare the message to be sent to Python
                                            const messageData = {
                                                id: messageId,
                                                extensionId: extensionId,
                                                message: message,
                                                options: options,
                                                source: window.location.href,
                                                type: 'runtime.sendMessage'
                                            };
                                            
                                            // Send the message through the transport
                                            if (qt && qt.webChannelTransport) {
                                                qt.webChannelTransport.send(JSON.stringify(messageData));
                                                console.log("Message sent through transport:", messageData);
                                            } else {
                                                console.error("qt.webChannelTransport not available for sending message");
                                            }
                                            
                                            // Notify any registered message listeners in this context
                                            setTimeout(() => {
                                                const messageListeners = chrome.runtime.onMessage.listeners;
                                                for (let i = 0; i < messageListeners.length; i++) {
                                                    try {
                                                        const listener = messageListeners[i];
                                                        const sender = { id: extensionId };
                                                        const sendResponse = function(response) {
                                                            console.log("sendResponse called with:", response);
                                                            if (typeof callback === 'function') {
                                                                callback(response);
                                                            }
                                                        };
                                                        
                                                        listener(message, sender, sendResponse);
                                                    } catch (e) {
                                                        console.error("Error in message listener:", e);
                                                    }
                                                }
                                            }, 0);
                                            
                                            return true;
                                        };
                                        
                                        // Implement connect with proper port objects
                                        chrome.runtime.connect = function(extensionIdOrOptions, options) {
                                            console.log("chrome.runtime.connect called:", extensionIdOrOptions, options);
                                            
                                            // Handle different parameter combinations
                                            let extensionId, connectInfo;
                                            
                                            if (typeof extensionIdOrOptions === 'string') {
                                                extensionId = extensionIdOrOptions;
                                                connectInfo = options || {};
                                            } else {
                                                extensionId = chrome.runtime.id;
                                                connectInfo = extensionIdOrOptions || {};
                                            }
                                            
                                            // Generate a unique port ID
                                            const portId = 'port_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
                                            
                                            // Create a port object
                                            const port = {
                                                name: connectInfo.name || '',
                                                sender: { id: extensionId },
                                                disconnect: function() {
                                                    console.log("Port disconnected:", portId);
                                                    // Notify Python about the disconnection
                                                    if (qt && qt.webChannelTransport) {
                                                        qt.webChannelTransport.send(JSON.stringify({
                                                            type: 'runtime.disconnect',
                                                            portId: portId
                                                        }));
                                                    }
                                                    
                                                    // Notify any disconnect listeners
                                                    const disconnectListeners = port.onDisconnect.listeners;
                                                    for (let i = 0; i < disconnectListeners.length; i++) {
                                                        try {
                                                            disconnectListeners[i](port);
                                                        } catch (e) {
                                                            console.error("Error in port disconnect listener:", e);
                                                        }
                                                    }
                                                },
                                                postMessage: function(message) {
                                                    console.log("Port.postMessage called:", { portId, message });
                                                    // Send the message through the transport
                                                    if (qt && qt.webChannelTransport) {
                                                        qt.webChannelTransport.send(JSON.stringify({
                                                            type: 'runtime.portMessage',
                                                            portId: portId,
                                                            message: message
                                                        }));
                                                    }
                                                    
                                                    // Simulate a response for testing
                                                    setTimeout(() => {
                                                        const messageListeners = port.onMessage.listeners;
                                                        for (let i = 0; i < messageListeners.length; i++) {
                                                            try {
                                                                messageListeners[i]({
                                                                    received: true,
                                                                    echo: message,
                                                                    from: 'simulated',
                                                                    timestamp: Date.now()
                                                                });
                                                            } catch (e) {
                                                                console.error("Error in port message listener:", e);
                                                            }
                                                        }
                                                    }, 50);
                                                },
                                                onMessage: createEventTarget(),
                                                onDisconnect: createEventTarget()
                                            };
                                            
                                            // Store the port for later use
                                            if (!window.__ports) {
                                                window.__ports = {};
                                            }
                                            window.__ports[portId] = port;
                                            
                                            // Notify Python about the connection
                                            if (qt && qt.webChannelTransport) {
                                                qt.webChannelTransport.send(JSON.stringify({
                                                    type: 'runtime.connect',
                                                    portId: portId,
                                                    extensionId: extensionId,
                                                    connectInfo: connectInfo
                                                }));
                                            }
                                            
                                            // Notify listeners about the connection
                                            const connectListeners = window.chrome.runtime.onConnect.listeners;
                                            for (let i = 0; i < connectListeners.length; i++) {
                                                try {
                                                    connectListeners[i](port);
                                                } catch (e) {
                                                    console.error("Error in onConnect listener:", e);
                                                }
                                            }
                                            
                                            return port;
                                        };
                                        
                                        // Function to handle incoming messages from Python
                                        window.__handleRuntimeMessage = function(message, sender) {
                                            console.log("Handling runtime message:", message, sender);
                                            
                                            // Ensure chrome.runtime.onMessage exists and has listeners
                                            if (!chrome.runtime || !chrome.runtime.onMessage || !chrome.runtime.onMessage.listeners) {
                                                console.error("chrome.runtime.onMessage not properly initialized");
                                                return;
                                            }
                                            
                                            const messageListeners = chrome.runtime.onMessage.listeners;
                                            console.log("Number of message listeners:", messageListeners.length);
                                            
                                            let responseSent = false;
                                            
                                            for (let i = 0; i < messageListeners.length; i++) {
                                                try {
                                                    const listener = messageListeners[i];
                                                    if (typeof listener !== 'function') {
                                                        console.error("Listener is not a function:", listener);
                                                        continue;
                                                    }
                                                    
                                                    const sendResponse = function(response) {
                                                        if (responseSent) {
                                                            console.warn("sendResponse called more than once");
                                                            return;
                                                        }
                                                        responseSent = true;
                                                        console.log("Sending response:", response);
                                                        
                                                        // Send the response back through the transport
                                                        if (qt && qt.webChannelTransport) {
                                                            qt.webChannelTransport.send(JSON.stringify({
                                                                type: 'runtime.sendResponse',
                                                                messageId: message.id,
                                                                response: response
                                                            }));
                                                        }
                                                    };
                                                    
                                                    const keepChannelOpen = listener(message.data, sender, sendResponse);
                                                    if (!keepChannelOpen && !responseSent) {
                                                        // If the listener didn't return true and didn't call sendResponse,
                                                        // we should close the message channel
                                                        responseSent = true;
                                                    }
                                                } catch (e) {
                                                    console.error("Error in message listener:", e);
                                                }
                                            }
                                        };
                                        
                                        // Function to handle incoming port messages from Python
                                        window.__handlePortMessage = function(portId, message) {
                                            console.log("Handling port message:", portId, message);
                                            
                                            if (window.__ports && window.__ports[portId]) {
                                                const port = window.__ports[portId];
                                                const messageListeners = port.onMessage.listeners;
                                                
                                                for (let i = 0; i < messageListeners.length; i++) {
                                                    try {
                                                        messageListeners[i](message);
                                                    } catch (e) {
                                                        console.error("Error in port message listener:", e);
                                                    }
                                                }
                                            } else {
                                                console.error("Port not found:", portId);
                                            }
                                        };
                                        
                                        console.log("chrome.runtime API initialized with event listeners");
                                    }, 10);
                                }
                            } else if (jsonMessage.type === "invoke") {
                                // Simulate a response to method invocation
                                if (this.onmessage) {
                                    var response = {
                                        "id": jsonMessage.id,
                                        "data": {}
                                    };
                                    
                                    // Handle specific method calls
                                    if (jsonMessage.method === "extensionLoaded") {
                                        console.log("Extension loaded method called with args:", jsonMessage.args);
                                        // No specific response needed
                                    } else if (jsonMessage.method === "getManifest") {
                                        response.data = JSON.stringify({
                                            "name": "Test Extension",
                                            "version": "1.0",
                                            "manifest_version": 3,
                                            "permissions": ["storage"]
                                        });
                                    } else if (jsonMessage.method === "sendMessage") {
                                        console.log("sendMessage called with args:", jsonMessage.args);
                                        // Simulate async response via callback
                                        setTimeout(() => {
                                            var callbackResponse = {
                                                "id": jsonMessage.id + "_callback",
                                                "data": JSON.stringify({
                                                    "success": true,
                                                    "from": "background",
                                                    "received": true,
                                                    "timestamp": Date.now()
                                                })
                                            };
                                            this.onmessage(JSON.stringify(callbackResponse));
                                            
                                            // Also notify any registered message listeners
                                            if (chrome && chrome.runtime && chrome.runtime.onMessage) {
                                                const message = JSON.parse(jsonMessage.args[1]);
                                                chrome.runtime.onMessage.listeners.forEach(listener => {
                                                    try {
                                                        listener(message, { id: chrome.runtime.id }, function(response) {
                                                            console.log("Message listener response:", response);
                                                        });
                                                    } catch (e) {
                                                        console.error("Error in message listener:", e);
                                                    }
                                                });
                                            }
                                        }, 50);
                                    }
                                    
                                    this.onmessage(JSON.stringify(response));
                                }
                            }
                        } catch (e) {
                            console.error("Error processing message:", e);
                        }
                    },
                    onmessage: null
                };
            }
            
            // Set the global flag that the runtime API checks
            window.__qtWebChannelTransportReady = true;
            
            // Dispatch events to notify listeners that the transport is ready
            document.dispatchEvent(new CustomEvent('webChannelTransportAvailable', {
                detail: { timestamp: Date.now() }
            }));
            
            // Create a mock channel object with the necessary structure
            const mockChannel = {
                objects: {
                    runtimeBridge: {
                        __id__: "runtimeBridge",
                        messageReceived: {
                            connect: function(callback) {
                                console.log("Connected to messageReceived signal");
                                // Store the callback for later use
                                if (!window.__messageReceivedCallbacks) {
                                    window.__messageReceivedCallbacks = [];
                                }
                                window.__messageReceivedCallbacks.push(callback);
                            }
                        },
                        portMessageReceived: {
                            connect: function(callback) {
                                console.log("Connected to portMessageReceived signal");
                                // Store the callback for later use
                                if (!window.__portMessageReceivedCallbacks) {
                                    window.__portMessageReceivedCallbacks = [];
                                }
                                window.__portMessageReceivedCallbacks.push(callback);
                            }
                        }
                    }
                },
                execCallbacks: {}
            };
            
            // Store the mock channel globally
            window.channel = mockChannel;
            
            document.dispatchEvent(new CustomEvent('webChannelReady', {
                detail: { channel: mockChannel }
            }));
            
            console.log("Transport object made global and events dispatched");
            return true;
        })()
        """
        
        # Run the script to make the transport object global
        page.runJavaScript(make_transport_global_script, lambda result:
            print(f"Made transport object global: {result}"))
        
        # Then run the main initialization script
        script = """
// Global tracking variables
window.__qtWebChannelTransportReady = false;
window.__qwebchannel_init_attempts = 0;
const MAX_RETRIES = 50;

function initializeWebChannel() {
    if (typeof qt !== 'undefined' && qt.webChannelTransport) {
        try {
            // Check if QWebChannel is defined
            if (typeof QWebChannel === 'undefined') {
                console.log("QWebChannel is not defined yet, will retry later");
                return false;
            }
            
            console.log("Creating new QWebChannel with transport:", qt.webChannelTransport);
            new QWebChannel(qt.webChannelTransport, function(channel) {
                window.channel = channel;
                
                if (channel.objects && channel.objects.runtimeBridge) {
                    window.runtimeBridge = channel.objects.runtimeBridge;
                    window.__qtWebChannelTransportReady = true;
                    console.log("QWebChannel initialized successfully with RuntimeBridge");
                    
                    // Make chrome.runtime available globally
                    if (window.runtimeBridge) {
                        console.log("RuntimeBridge is available, initializing chrome.runtime API");
                        // Expose chrome.runtime API
                        if (typeof chrome === 'undefined') {
                            window.chrome = {};
                        }
                        if (typeof chrome.runtime === 'undefined') {
                            chrome.runtime = {};
                        }
                        
                        // Define basic chrome.runtime API methods
                        chrome.runtime.id = window.location.hostname;
                        chrome.runtime.sendMessage = function(extensionId, message, options, callback) {
                            if (typeof extensionId !== 'string') {
                                callback = options;
                                options = message;
                                message = extensionId;
                                extensionId = window.location.hostname;
                            }
                            // Always pass an options parameter, even if it's empty
                            if (typeof options === 'undefined') {
                                options = {};
                            }
                            return window.runtimeBridge.sendMessage(extensionId, JSON.stringify(message), JSON.stringify(options));
                        };
                    }
                } else {
                    console.error("Channel created but runtimeBridge is not available:", channel.objects);
                }
                
                // Dispatch an event to notify that the channel is ready
                document.dispatchEvent(new CustomEvent('webChannelReady', {
                    detail: { channel: channel }
                }));
                
                // Also dispatch the chrome.runtime.initialized event
                document.dispatchEvent(new CustomEvent('chrome.runtime.initialized', {
                    detail: { extensionId: window.location.hostname }
                }));
            });
            return true;
        } catch (e) {
            console.error("Error initializing QWebChannel:", e);
            return false;
        }
    } else {
        console.log("qt.webChannelTransport not available yet, will retry later");
        return false;
    }
}

function checkTransport() {
    if (typeof qt !== 'undefined' && qt.webChannelTransport) {
        console.log('qt.webChannelTransport is available');
        return true;
    }
    return false;
}

// Check if QWebChannel is defined
function isQWebChannelDefined() {
    return typeof QWebChannel !== 'undefined';
}

// Try to initialize immediately
if (!isQWebChannelDefined()) {
    console.log("QWebChannel is not defined yet, waiting for it to load...");
    
    // Set up a retry mechanism specifically for QWebChannel loading
    let qwebchannelCheckCount = 0;
    const MAX_QWEBCHANNEL_CHECKS = 50;
    
    const qwebchannelCheckInterval = setInterval(function() {
        qwebchannelCheckCount++;
        console.log("Checking if QWebChannel is defined... (attempt " + qwebchannelCheckCount + " of " + MAX_QWEBCHANNEL_CHECKS + ")");
        
        if (isQWebChannelDefined()) {
            console.log("QWebChannel is now defined, attempting initialization");
            clearInterval(qwebchannelCheckInterval);
            
            // Now try to initialize with the transport
            if (!initializeWebChannel()) {
                // If initialization fails, set up the transport retry mechanism
                let transportRetryCount = 0;
                const MAX_TRANSPORT_RETRIES = 50;
                
                // Set up a MutationObserver to detect when qt object might be injected
                var observer = new MutationObserver(function(mutations) {
                    if (checkTransport() && !window.__qtWebChannelTransportReady) {
                        console.log("MutationObserver detected qt.webChannelTransport");
                        initializeWebChannel();
                        if (window.__qtWebChannelTransportReady) {
                            observer.disconnect();
                        }
                    }
                });
                
                // Start observing
                observer.observe(document, {
                    childList: true,
                    subtree: true,
                    attributes: true
                });
                
                // Also use interval-based retry
                const retryInterval = setInterval(function() {
                    transportRetryCount++;
                    console.log("Retrying QWebChannel initialization... (attempt " + transportRetryCount + " of " + MAX_TRANSPORT_RETRIES + ")");
                    
                    // Ensure qt and transport objects exist
                    if (typeof qt === 'undefined') {
                        console.log("Creating qt object during retry");
                        window.qt = {};
                    }
                    
                    if (!qt.webChannelTransport) {
                        console.log("Creating qt.webChannelTransport object during retry");
                        qt.webChannelTransport = {};
                    }
                    
                    if (initializeWebChannel() || transportRetryCount >= MAX_TRANSPORT_RETRIES) {
                        clearInterval(retryInterval);
                        observer.disconnect();
                        if (transportRetryCount >= MAX_TRANSPORT_RETRIES) {
                            console.error("Failed to initialize QWebChannel after " + MAX_TRANSPORT_RETRIES + " attempts");
                        }
                    }
                }, 200);
            }
        } else if (qwebchannelCheckCount >= MAX_QWEBCHANNEL_CHECKS) {
            console.error("QWebChannel was never defined after " + MAX_QWEBCHANNEL_CHECKS + " checks");
            clearInterval(qwebchannelCheckInterval);
        }
    }, 100); // Check more frequently for QWebChannel definition
} else if (!initializeWebChannel()) {
    // QWebChannel is defined but initialization failed, set up the transport retry mechanism
    let retryCount = 0;
    
    // Set up a MutationObserver to detect when qt object might be injected
    var observer = new MutationObserver(function(mutations) {
        if (checkTransport() && !window.__qtWebChannelTransportReady) {
            console.log("MutationObserver detected qt.webChannelTransport");
            initializeWebChannel();
            if (window.__qtWebChannelTransportReady) {
                observer.disconnect();
            }
        }
    });
    
    // Start observing
    observer.observe(document, {
        childList: true,
        subtree: true,
        attributes: true
    });
    
    // Also use interval-based retry
    const retryInterval = setInterval(function() {
        retryCount++;
        console.log("Retrying QWebChannel initialization... (attempt " + retryCount + " of " + MAX_RETRIES + ")");
        
        // Ensure qt and transport objects exist
        if (typeof qt === 'undefined') {
            console.log("Creating qt object during retry");
            window.qt = {};
        }
        
        if (!qt.webChannelTransport) {
            console.log("Creating qt.webChannelTransport object during retry");
            qt.webChannelTransport = {};
        }
        
        if (initializeWebChannel() || retryCount >= MAX_RETRIES) {
            clearInterval(retryInterval);
            observer.disconnect();
            if (retryCount >= MAX_RETRIES) {
                console.error("Failed to initialize QWebChannel after " + MAX_RETRIES + " attempts");
            }
        }
    }, 200);
    
    // Stop checking after 20 seconds to prevent memory leaks
    setTimeout(function() {
        observer.disconnect();
        clearInterval(retryInterval);
        if (!window.__qtWebChannelTransportReady) {
            console.error('Failed to initialize QWebChannel after 20 seconds');
        }
    }, 20000);
}

// Add an event listener for manual transport requests
document.addEventListener('requestWebChannelTransport', function() {
    console.log("Received request for webChannelTransport");
    if (typeof qt !== 'undefined' && qt.webChannelTransport) {
        console.log("qt.webChannelTransport is available, initializing QWebChannel");
        initializeWebChannel();
    } else {
        console.log("qt.webChannelTransport still not available after request");
        
        // Try to manually create the transport object as a last resort
        if (typeof qt === 'undefined') {
            console.log("Attempting to create qt object");
            window.qt = {};
        }
        
        if (!qt.webChannelTransport) {
            console.log("Creating qt.webChannelTransport object");
            qt.webChannelTransport = {};
        }
        
        // Notify that we need the transport object
        document.dispatchEvent(new CustomEvent('webChannelTransportRequested', {
            detail: { timestamp: Date.now() }
        }));
        
        // Try initialization again
        initializeWebChannel();
    }
});

// Helper function to check if chrome.runtime is initialized
window.isChromeRuntimeInitialized = function() {
    return window.__qtWebChannelTransportReady === true;
};
"""

    def on_load_finished(self, success):
        """Handle the page load finishing."""
        self.is_loading = False
        self.loadStatusChanged.emit()
        if success:
            current_url = self.web_view.url().toString()
            print(f"Page loaded successfully: {current_url}")
            
            # Update the title if available
            title = self.web_view.title()
            if title:
                self.titleChanged.emit(title)
            
            # Check if we need to inject content scripts
            from shmcs.qtbrowser.browser import Browser
            if hasattr(self.parent, 'window') and isinstance(self.parent.window(), Browser):
                browser = self.parent.window()
                if hasattr(browser, 'content_script_injector'):
                    # Only inject content scripts for http/https URLs
                    if current_url.startswith(('http://', 'https://')):
                        self._inject_content_scripts(current_url, browser.content_script_injector)
        else:
            print(f"Failed to load page: {self.url}")
    
    def _inject_content_scripts(self, url, injector):
        """Inject matching content scripts into the page."""
        matching_scripts = injector.get_scripts_for_url(url)
        if not matching_scripts:
            return
            
        print(f"Found {len(matching_scripts)} matching content scripts for {url}")
        
        for script_def in matching_scripts:
            # Create the injection script
            injection_script = injector.create_injection_script(script_def)
            
            # Inject the script into the page
            self.web_view.page().runJavaScript(
                injection_script,
                lambda result: print(f"Content script injection completed for {script_def['extension_id']}")
            )
            
            print(f"Injecting content scripts from {script_def['extension_id']}: {script_def['js']}")
    
    # We no longer need process handling methods since we're using QWebEngineView directly
    
    def close(self):
        """Close the tab."""
        # Close the web view
        if hasattr(self, 'web_view'):
            self.web_view.close()
        
        super().close()


