
import os
import json
import shutil
import re
import mimetypes
from PyQt6.QtCore import Qt, QBuffer
from PyQt6.QtWidgets import (
    QDialog, QVBoxLayout, QListWidget, QHBoxLayout, QPushButton,
    QFileDialog, QMessageBox, QListWidgetItem
)
from PyQt6.QtWebEngineCore import (
    QWebEnginePage, QWebEngineUrlSchemeHandler,
    QWebEngineUrlRequestJob
)


class ExtensionSchemeHandler(QWebEngineUrlSchemeHandler):
    """A custom URL scheme handler for loading extension files."""
    def __init__(self, extensions_dir, loaded_extensions, parent=None):
        super().__init__(parent)
        self.extensions_dir = extensions_dir
        self.loaded_extensions = loaded_extensions
        self.jobs = {}  # Keep track of active jobs and their buffers
        self.assets_extensions_dir = "assets/browser/extensions"  # Path to the source extensions


    def rewrite_src_urls(self, html_text, extension_id):
        def rewrite_url(match):
            # Group 1 is the attribute name (src), group 2 is the URL (quoted or unquoted)
            url = match.group(2)
            if (url.startswith('/') and
                not url.startswith(f'/{extension_id}/') and
                not url.startswith('data:') and
                '://' not in url):
                # Preserve the original quote style or lack thereof
                quote = match.group(3) or ''
                return f'src={quote}/{extension_id}{url}{quote}'
            return match.group(0)

        # Match src attributes: quoted (src="/url" or src='/url') or unquoted (src=/url)
        pattern = r'src=([\'"])?([^\'"\s>]+)([\'"])?'
        return re.sub(pattern, rewrite_url, html_text)

    def requestStarted(self, job: QWebEngineUrlRequestJob):
        url = job.requestUrl()
        ext_id = url.host()
        resource_path = url.path().lstrip('/')

        # First try to load from the profile's extensions directory
        file_path = os.path.abspath(os.path.join(self.extensions_dir, ext_id, resource_path))
        
        # If not found, try the assets extensions directory
        if not os.path.exists(file_path):
            assets_file_path = os.path.abspath(os.path.join(self.assets_extensions_dir, ext_id, resource_path))
            if os.path.exists(assets_file_path):
                file_path = assets_file_path
        
        try:
            with open(file_path, 'rb') as f:
                content = f.read()

            mime_type, _ = mimetypes.guess_type(file_path)
            if mime_type:
                mime_type = mime_type.encode()
            else:
                # Special handling for JavaScript files
                if file_path.endswith('.js'):
                    mime_type = b'application/javascript'
                else:
                    mime_type = b'application/octet-stream'

            # Special handling for HTML files - inject WebChannel initialization code
            if file_path.endswith(('.html', '.htm')) and mime_type in (b'text/html', b'application/xhtml+xml'):
                print(f"Injecting WebChannel initialization into HTML file: {file_path}")
                try:
                    # Decode the HTML content
                    html_content = content.decode('utf-8')
                    
                    # Load QWebChannel.js
                    qwebchannel_path = os.path.join("assets/browser/js", "qwebchannel.js")
                    with open(qwebchannel_path, 'r', encoding='utf-8') as f:
                        qwebchannel_js = f.read()
                    
                    # Create the WebChannel initialization script with direct QWebChannel.js injection
                    init_script = f"""
                    <script>
                    // Directly define QWebChannel in the global scope
                    {qwebchannel_js}
                    
                    console.log("QWebChannel.js directly injected into extension page");
                    
                    // Verify QWebChannel is defined
                    if (typeof QWebChannel === 'undefined') {{
                        console.error("QWebChannel is still undefined after direct injection!");
                    }} else {{
                        console.log("QWebChannel is successfully defined in global scope");
                    }}
                    
                    // WebChannel initialization
                    window.__qtWebChannelTransportReady = false;
                    
                    function initializeWebChannel() {{
                        if (typeof qt !== 'undefined' && qt.webChannelTransport) {{
                            try {{
                                // QWebChannel should be defined now
                                new QWebChannel(qt.webChannelTransport, function(channel) {{
                                    window.channel = channel;
                                    window.runtimeBridge = channel.objects.runtimeBridge;
                                    window.__qtWebChannelTransportReady = true;
                                    console.log("QWebChannel initialized successfully for extension: {ext_id}");
                                    
                                    // 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: "{ext_id}" }}
                                    }}));
                                    
                                    // 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 = "{ext_id}";
                                        chrome.runtime.sendMessage = function(extensionId, message, options, callback) {{
                                            if (typeof extensionId !== 'string') {{
                                                callback = options;
                                                options = message;
                                                message = extensionId;
                                                extensionId = "{ext_id}";
                                            }}
                                            // 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));
                                        }};
                                    }}
                                }});
                                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;
                        }}
                    }}
                    
                    // Create a global variable to track initialization attempts
                    window.__qwebchannel_init_attempts = 0;
                    
                    // Try to initialize immediately
                    if (!initializeWebChannel()) {{
                        // If it fails, set up a retry mechanism
                        const MAX_RETRIES = 50;
                        
                        const retryInterval = setInterval(function() {{
                            window.__qwebchannel_init_attempts++;
                            console.log("Retrying QWebChannel initialization... (attempt " + window.__qwebchannel_init_attempts + " of " + MAX_RETRIES + ")");
                            
                            // Check if qt object exists, if not, try to create it
                            if (typeof qt === 'undefined') {{
                                console.log("qt object is undefined, creating placeholder");
                                window.qt = {{}};
                            }}
                            
                            if (initializeWebChannel() || window.__qwebchannel_init_attempts >= MAX_RETRIES) {{
                                clearInterval(retryInterval);
                                if (window.__qwebchannel_init_attempts >= MAX_RETRIES) {{
                                    console.error("Failed to initialize QWebChannel after " + MAX_RETRIES + " attempts");
                                }}
                            }}
                        }}, 200);
                        
                        // 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();
                            }}
                        }});
                    }}
                    </script>
                    """
                    
                    if ext_id:
                      # Inject URL modifier
                      init_script = init_script + f"""
                      <script>
                              (function() {{
                                  console.log("Starting --------------------------------------------------------------");
                                  const extensionId = '{ext_id}';
                                  function rewriteUrl(url) {{
                                      // Rewrite relative URLs starting with '/' but not already containing the extension_id
                                      if (!url.startsWith('/') || url.startsWith(`/${{extensionId}}/`) || url.startsWith('data:') || url.includes('://')) {{
                                          return url;
                                      }}
                                      return `/${{extensionId}}${{url}}`;
                                  }}
                                  function rewriteHtmlAttributes() {{
                                      console.log("Rewriting attributes");
                                      const selectors = ['script[src]', 'link[href]', 'img[src]', 'source[src]', 'iframe[src]'];
                                      selectors.forEach(selector => {{
                                          document.querySelectorAll(selector).forEach(element => {{
                                              const attr = element.src ? 'src' : 'href';
                                              const originalUrl = element.getAttribute(attr);
                                              const newUrl = rewriteUrl(originalUrl);
                                              if (newUrl !== originalUrl) {{
                                                  element.setAttribute(attr, newUrl);
                                                                    }}
                                          }});
                                      }});
                                  }}
                                  // Intercept fetch
                                  const originalFetch = window.fetch;
                                  window.fetch = function(url, options) {{
                                      if (typeof url === 'string') {{
                                          url = rewriteUrl(url);
                                      }} else if (url instanceof Request) {{
                                          url = new Request(rewriteUrl(url.url), url);
                                      }}
                                      return originalFetch.call(this, url, options);
                                  }};
                                  // Intercept XMLHttpRequest
                                  const originalXhrOpen = XMLHttpRequest.prototype.open;
                                  XMLHttpRequest.prototype.open = function(method, url, ...args) {{
                                      url = rewriteUrl(url);
                                      return originalXhrOpen.call(this, method, url, ...args);
                                  }};
                                  // Run immediately and on DOM changes
                                  rewriteHtmlAttributes();

                                  // Set up MutationObserver safely
                                  function setupObserver() {{
                                      rewriteHtmlAttributes();
                                      const target = document.body || document.documentElement;
                                      if (!target) {{
                                          // Retry if neither body nor documentElement is available
                                          setTimeout(setupObserver, 10);
                                          return;
                                      }}
                                      const observer = new MutationObserver(rewriteHtmlAttributes);
                                      observer.observe(target, {{ childList: true, subtree: true }});
                                      console.log("Observer done");
                                  }}

                                  // Run observer setup after DOM is ready or immediately if possible
                                  if (document.readyState === 'loading') {{
                                      document.addEventListener('DOMContentLoaded', setupObserver);
                                  }} else {{
                                      setupObserver();
                                  }}
								  """+"""
                                  const scriptobserver = new MutationObserver((mutations) => {
                                     mutations.forEach((mutation) => {
                                       // Check for added nodes
                                       mutation.addedNodes.forEach((node) => {
                                         if (node.tagName === 'SCRIPT' && node.src) {
                                              newsrc = rewriteUrl(node.src);
                                              if (newsrc != node.src) {
												 node.src = newsrc;
												 console.log(node, "update src");
											  }
                                         }
                                       });
                                     });
                                  });

                                  // Start observing the document with the configured parameters
                                  scriptobserver.observe(document, {
                                     childList: true,
                                     subtree: true
                                  });
                               })();

                              </script>
                              """

                    # Fix URL handling in extensions
                    if ext_id:
                      html_content = self.rewrite_src_urls(html_content, ext_id)


                    # Inject the script right before the closing </head> tag
                    if '</head>' in html_content:
                        html_content = html_content.replace('</head>', init_script + '</head>')
                    else:
                        # If no </head> tag, inject at the beginning of the file
                        html_content = init_script + html_content
                    
                    # Convert back to bytes
                    content = html_content.encode('utf-8')

                    
                except Exception as e:
                    print(f"Error injecting WebChannel initialization: {e}")
                    # Continue with the original content if there's an error
            
            # Special handling for service worker scripts
            if resource_path.endswith('background.js'):
                print(f"Loading background script: {file_path}")
                # You might want to add special headers or processing for service workers


            buf = QBuffer(parent=self)
            buf.setData(content)
            buf.open(QBuffer.OpenModeFlag.ReadOnly)

            # Store buffer to prevent garbage collection
            self.jobs[job] = buf

            # Reply with the content
            job.reply(mime_type, buf)
            print(f"Replied with content for: {file_path}")
        except FileNotFoundError as e:
            print(f"Extension resource not found: {file_path} {e}")
            job.fail(QWebEngineUrlRequestJob.Error.UrlNotFound)
            if job in self.jobs:
                del self.jobs[job]
        except Exception as e:
            print(f"Error loading extension resource {file_path}: {e}")
            job.fail(QWebEngineUrlRequestJob.Error.RequestFailed)
            if job in self.jobs:
                del self.jobs[job]

class ExtensionDialog(QDialog):
    """
    Dialog for managing browser extensions.
    """
    def __init__(self, parent=None, extensions_dir=None):
        super().__init__(parent)
        self.setWindowTitle("Browser Extensions")
        self.setMinimumSize(500, 400)
        
        self.extensions_dir = extensions_dir
        self.open_popups = []
        
        # Create layout
        layout = QVBoxLayout(self)
        
        # Create extensions list
        self.extensions_list = QListWidget()
        layout.addWidget(self.extensions_list)
        
        # Create buttons
        button_layout = QHBoxLayout()
        self.install_button = QPushButton("Install New")
        self.remove_button = QPushButton("Remove")
        self.enable_button = QPushButton("Enable/Disable")
        self.popup_button = QPushButton("Open Popup")
        self.close_button = QPushButton("Close")
        
        button_layout.addWidget(self.install_button)
        button_layout.addWidget(self.remove_button)
        button_layout.addWidget(self.enable_button)
        button_layout.addWidget(self.popup_button)
        button_layout.addStretch()
        button_layout.addWidget(self.close_button)
        
        layout.addLayout(button_layout)
        
        # Connect signals
        self.close_button.clicked.connect(self.accept)
        self.install_button.clicked.connect(self.install_extension)
        self.remove_button.clicked.connect(self.remove_extension)
        self.enable_button.clicked.connect(self.toggle_extension)
        self.popup_button.clicked.connect(self.open_extension_popup)
        
        # Load extensions
        self.load_extensions()
    
    def load_extensions(self):
        """Load and display installed extensions."""
        self.extensions_list.clear()
        
        if not self.extensions_dir or not os.path.exists(self.extensions_dir):
            self.extensions_list.addItem("No extensions directory found")
            return
        
        # Find all extension directories
        for ext_name in os.listdir(self.extensions_dir):
            ext_path = os.path.join(self.extensions_dir, ext_name)
            if os.path.isdir(ext_path):
                enabled = not ext_name.endswith(".disabled")
                base_name = ext_name[:-9] if not enabled else ext_name
                
                manifest_path = os.path.join(ext_path, "manifest.json")
                if os.path.exists(manifest_path):
                    try:
                        with open(manifest_path, 'r', encoding='utf-8') as f:
                            manifest = json.load(f)
                        
                        name = manifest.get("name", base_name)
                        version = manifest.get("version", "unknown")
                        status = "ACTIVE" if enabled else "INACTIVE"
                        
                        item = QListWidgetItem(f"{name} (v{version}) - {status}")
                        item.setData(Qt.ItemDataRole.UserRole, ext_path)
                        item.setCheckState(Qt.CheckState.Checked if enabled else Qt.CheckState.Unchecked)
                        
                        self.extensions_list.addItem(item)
                    except Exception as e:
                        print(f"Error loading extension manifest for {ext_name}: {e}")
                        item = QListWidgetItem(f"{ext_name} (Error loading manifest)")
                        item.setData(Qt.ItemDataRole.UserRole, ext_path)
                        self.extensions_list.addItem(item)
    
    def install_extension(self):
        """Install a new extension from a directory."""
        source_dir = QFileDialog.getExistingDirectory(self, "Select Extension Directory")
        if not source_dir:
            return
            
        ext_name = os.path.basename(source_dir)
        dest_dir = os.path.join(self.extensions_dir, ext_name)
        
        if os.path.exists(dest_dir):
            QMessageBox.warning(self, "Extension Exists", f"An extension named '{ext_name}' already exists.")
            return
            
        try:
            shutil.copytree(source_dir, dest_dir)
            self.load_extensions()
            QMessageBox.information(self, "Restart Required",
                                    "The extension has been installed. Please restart the browser for it to be loaded.")
        except Exception as e:
            QMessageBox.critical(self, "Installation Error", f"Could not install the extension: {e}")
    
    def remove_extension(self):
        """Remove the selected extension."""
        selected_items = self.extensions_list.selectedItems()
        if not selected_items:
            return
        
        for item in selected_items:
            ext_path = item.data(Qt.ItemDataRole.UserRole)
            if ext_path and os.path.exists(ext_path):
                try:
                    shutil.rmtree(ext_path)
                    print(f"Removed extension: {ext_path}")
                except Exception as e:
                    print(f"Error removing extension: {e}")
        
        self.load_extensions()
    
    def toggle_extension(self):
        """Toggle the enabled state of the selected extension and reload the profile."""
        selected_items = self.extensions_list.selectedItems()
        if not selected_items:
            return
        
        item = selected_items[0]
        ext_path = item.data(Qt.ItemDataRole.UserRole)
        ext_name = os.path.basename(ext_path)
        is_enabled = not ext_name.endswith(".disabled")
        
        if is_enabled:
            new_path = ext_path + ".disabled"
        else:
            new_path = ext_path[:-9]
            
        try:
            # Rename the folder to enable/disable the extension
            os.rename(ext_path, new_path)
            
            # Update the list in the dialog
            self.load_extensions()
            
            # Get the main browser window and trigger a profile reload
            browser = self.parent()
            if browser and hasattr(browser, 'reload_profile_and_tabs'):
                browser.reload_profile_and_tabs()
            else:
                # This is a fallback in case the parent isn't the browser window
                QMessageBox.information(self, "Restart Required",
                                        "Change applied. Please restart the browser for it to take effect.")

        except OSError as e:
            QMessageBox.critical(self, "Error", f"Could not change extension state: {e}")

    def open_extension_popup(self):
        """Open the popup of the selected extension, if it has one."""
        selected_items = self.extensions_list.selectedItems()
        if not selected_items:
            return
            
        item = selected_items[0]
        ext_path = item.data(Qt.ItemDataRole.UserRole)
        ext_name = os.path.basename(ext_path)

        if ext_name.endswith(".disabled"):
            QMessageBox.warning(self, "Extension Disabled", "Cannot open popup for a disabled extension.")
            return

        manifest_path = os.path.join(ext_path, "manifest.json")

        if not os.path.exists(manifest_path):
            QMessageBox.warning(self, "Error", "manifest.json not found for this extension.")
            return

        try:
            with open(manifest_path, 'r', encoding='utf-8') as f:
                manifest = json.load(f)
        except Exception as e:
            QMessageBox.critical(self, "Error", f"Could not read manifest.json: {e}")
            return

        popup_path = None
        action = manifest.get('action') or manifest.get('browser_action') or manifest.get('page_action')
        if action and action.get('default_popup'):
            popup_path = action.get('default_popup')

        if not popup_path:
            QMessageBox.information(self, "No Popup", "This extension does not have a popup.")
            return

        popup_file = os.path.join(ext_path, popup_path)
        if not os.path.exists(popup_file):
            QMessageBox.warning(self, "Error", f"Popup file not found: {popup_path}")
            return

        # Open the popup in a new dialog
        popup_dialog = QDialog(self)
        popup_dialog.setWindowTitle(manifest.get("name", "Extension Popup"))
        layout = QVBoxLayout(popup_dialog)
        web_view = QWebEngineView()
        
        browser_window = self.parent()
        from shmcs.qtbrowser.browser import Browser
        if not browser_window or not isinstance(browser_window, Browser):
             QMessageBox.critical(self, "Error", "Could not get browser window reference.")
             return

        # Use the main browser's profile so the scheme handler is available
        page = QWebEnginePage(browser_window.profile, web_view)
        web_view.setPage(page)
        
        # Set up QWebChannel for the page to enable chrome.runtime API
        page.setWebChannel(browser_window.web_channel)
        
        # First load QWebChannel.js content
        qwebchannel_path = os.path.join("assets/browser/js", "qwebchannel.js")
        qwebchannel_js = ""
        try:
            with open(qwebchannel_path, 'r', encoding='utf-8') as f:
                qwebchannel_js = f.read()
        except Exception as e:
            print(f"Error loading QWebChannel.js: {e}")
            qwebchannel_js = "console.error('Failed to load QWebChannel.js');"
        
        # Explicitly initialize the QWebChannel with a script that includes QWebChannel.js
        init_script = f"""
            // Directly inject QWebChannel.js content
            {qwebchannel_js}
            
            console.log("QWebChannel.js directly injected into popup");
            
            // Verify QWebChannel is defined
            if (typeof QWebChannel === 'undefined') {{
                console.error("QWebChannel is still undefined after direct injection!");
            }} else {{
                console.log("QWebChannel is successfully defined in global scope");
            }}
            
            // Create a global variable to track if transport is ready
            window.__qtWebChannelTransportReady = false;
            
            function initializeWebChannel() {{
                if (typeof qt !== 'undefined' && qt.webChannelTransport) {{
                    try {{
                        // QWebChannel should be defined now
                        new QWebChannel(qt.webChannelTransport, function(channel) {{
                            window.channel = channel;
                            window.runtimeBridge = channel.objects.runtimeBridge;
                            window.__qtWebChannelTransportReady = true;
                            console.log("QWebChannel initialized successfully for extension popup");
                            
                            // 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: "{ext_name}" }}
                            }}));
                            
                            // 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 = "{ext_name}";
                                chrome.runtime.sendMessage = function(extensionId, message, options, callback) {{
                                    if (typeof extensionId !== 'string') {{
                                        callback = options;
                                        options = message;
                                        message = extensionId;
                                        extensionId = "{ext_name}";
                                    }}
                                    // 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));
                                }};
                            }}
                        }});
                        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;
                }}
            }}
            
            // Create a global variable to track initialization attempts
            window.__qwebchannel_init_attempts = 0;
            
            // Try to initialize immediately
            if (!initializeWebChannel()) {{
                // If it fails, set up a retry mechanism
                const MAX_RETRIES = 50;
                
                const retryInterval = setInterval(function() {{
                    window.__qwebchannel_init_attempts++;
                    console.log("Retrying QWebChannel initialization... (attempt " + window.__qwebchannel_init_attempts + " of " + MAX_RETRIES + ")");
                    
                    // Check if qt object exists, if not, try to create it
                    if (typeof qt === 'undefined') {{
                        console.log("qt object is undefined, creating placeholder");
                        window.qt = {{}};
                    }}
                    
                    if (initializeWebChannel() || window.__qwebchannel_init_attempts >= MAX_RETRIES) {{
                        clearInterval(retryInterval);
                        if (window.__qwebchannel_init_attempts >= MAX_RETRIES) {{
                            console.error("Failed to initialize QWebChannel after " + MAX_RETRIES + " attempts");
                        }}
                    }}
                }}, 200);
            }}
        """
        
        # Run the initialization script before loading the URL
        page.runJavaScript(init_script, lambda result: print("QWebChannel initialization script executed for popup"))

        popup_url = QUrl(f"qextension://{ext_name}/{popup_path}")
        web_view.load(popup_url)

        layout.addWidget(web_view)
        popup_dialog.setLayout(layout)
        popup_dialog.resize(400, 600)
        popup_dialog.show()
        self.open_popups.append(popup_dialog)
        popup_dialog.finished.connect(lambda: self.open_popups.remove(popup_dialog))



class ContentScriptInjector:
    """
    Handles the injection of extension content scripts into web pages.
    Parses extension manifests to find content scripts and injects them
    into matching web pages.
    """
    def __init__(self, extensions_dir, assets_extensions_dir="assets/browser/extensions"):
        self.extensions_dir = extensions_dir
        self.assets_extensions_dir = assets_extensions_dir
        self.content_scripts = []  # List of content script definitions
        self.load_content_scripts()
    
    def load_content_scripts(self):
        """Load content script definitions from all installed extensions."""
        print("Loading content scripts from extensions...")
        self.content_scripts = []
        
        # First check the profile extensions directory
        if os.path.exists(self.extensions_dir):
            for ext_name in os.listdir(self.extensions_dir):
                if ext_name.endswith(".disabled"):
                    continue
                    
                ext_path = os.path.join(self.extensions_dir, ext_name)
                if os.path.isdir(ext_path):
                    self._load_extension_content_scripts(ext_path, ext_name)
        
        # Then check the assets extensions directory
        if os.path.exists(self.assets_extensions_dir):
            for ext_name in os.listdir(self.assets_extensions_dir):
                if ext_name.endswith(".disabled"):
                    continue
                    
                ext_path = os.path.join(self.assets_extensions_dir, ext_name)
                if os.path.isdir(ext_path):
                    # Only load if not already loaded from profile
                    if not os.path.exists(os.path.join(self.extensions_dir, ext_name)):
                        self._load_extension_content_scripts(ext_path, ext_name)
        
        print(f"Loaded {len(self.content_scripts)} content script definitions")
    
    def _load_extension_content_scripts(self, ext_path, ext_name):
        """Load content scripts from a specific extension."""
        manifest_path = os.path.join(ext_path, "manifest.json")
        if not os.path.exists(manifest_path):
            return
            
        try:
            with open(manifest_path, 'r', encoding='utf-8') as f:
                manifest = json.load(f)
                
            if 'content_scripts' not in manifest:
                return
                
            for script_def in manifest['content_scripts']:
                if 'matches' not in script_def or 'js' not in script_def:
                    continue
                    
                # Add the extension path and ID to the definition
                script_def['extension_path'] = ext_path
                script_def['extension_id'] = ext_name
                self.content_scripts.append(script_def)
                
                print(f"Loaded content script from {ext_name}: {script_def['js']}")
        except Exception as e:
            print(f"Error loading content scripts from {ext_name}: {e}")
    
    def get_scripts_for_url(self, url):
        """Get all content scripts that match the given URL."""
        matching_scripts = []
        
        for script_def in self.content_scripts:
            if self._url_matches_patterns(url, script_def['matches']):
                matching_scripts.append(script_def)
                
        return matching_scripts
    
    def _url_matches_patterns(self, url, patterns):
        """Check if a URL matches any of the given patterns."""
        for pattern in patterns:
            # Handle special pattern <all_urls>
            if pattern == "<all_urls>":
                return True
                
            # Convert the match pattern to a regex
            regex_pattern = self._convert_match_pattern_to_regex(pattern)
            if regex_pattern and re.match(regex_pattern, url):
                return True
                
        return False
    
    def _convert_match_pattern_to_regex(self, pattern):
        """Convert a Chrome extension match pattern to a regex."""
        # Handle special cases
        if pattern == "<all_urls>":
            return r"^(http|https|file|ftp)://.*"
            
        # Basic pattern validation
        if not pattern or not ("://" in pattern):
            return None
            
        # Parse the pattern
        parts = pattern.split("://")
        if len(parts) != 2:
            return None
            
        scheme, rest = parts
        
        # Handle scheme
        if scheme == "*":
            scheme_regex = "(http|https)"
        elif scheme in ["http", "https", "file", "ftp"]:
            scheme_regex = scheme
        else:
            return None
            
        # Split host and path
        if "/" in rest:
            host, path = rest.split("/", 1)
            path = "/" + path
        else:
            host = rest
            path = "/"
            
        # Handle host
        if host == "*":
            host_regex = ".*"
        elif host.startswith("*."):
            host_regex = "(.*\\.)?" + re.escape(host[2:])
        else:
            host_regex = re.escape(host)
            
        # Handle path
        path_regex = ""
        if path == "/*":
            path_regex = "/.*"
        elif path.endswith("/*"):
            path_regex = re.escape(path[:-1]) + ".*"
        else:
            path_regex = re.escape(path)
            
        # Combine into final regex
        return f"^{scheme_regex}://{host_regex}{path_regex}$"
    
    def create_injection_script(self, script_def, web_channel_init=True):
        """Create a script to inject the content scripts into the page."""
        extension_id = script_def['extension_id']
        extension_path = script_def['extension_path']
        js_files = script_def['js']
        
        # Build the injection script
        injection_script = ""
        
        # Add WebChannel initialization if requested
        if web_channel_init:
            # First load QWebChannel.js content
            qwebchannel_path = os.path.join("assets/browser/js", "qwebchannel.js")
            qwebchannel_js = ""
            try:
                with open(qwebchannel_path, 'r', encoding='utf-8') as f:
                    qwebchannel_js = f.read()
            except Exception as e:
                print(f"Error loading QWebChannel.js for content script: {e}")
                qwebchannel_js = "console.error('Failed to load QWebChannel.js');"
            
            injection_script += f"""
            // Initialize WebChannel for content script
            (function() {{
                // Directly inject QWebChannel.js content
                {qwebchannel_js}
                
                console.log("QWebChannel.js directly injected into content script <ContentScriptInjector.create_injection_script>");
                
                // Verify QWebChannel is defined
                if (typeof QWebChannel === 'undefined') {{
                    console.error("QWebChannel is still undefined after direct injection in content script!");
                }} else {{
                    console.log("QWebChannel is successfully defined in content script global scope");
                }}
                
                window.__contentScriptExtensionId = "{extension_id}";
                
                // Create a global variable to track if transport is ready
                window.__qtWebChannelTransportReady = false;
                
                function initializeWebChannel() {{
                    if (typeof qt !== 'undefined' && qt.webChannelTransport) {{
                        try {{
                            // QWebChannel should be defined now
                            new QWebChannel(qt.webChannelTransport, function(channel) {{
                                window.channel = channel;
                                window.runtimeBridge = channel.objects.runtimeBridge;
                                window.__qtWebChannelTransportReady = true;
                                console.log("QWebChannel initialized successfully for content script");
                                
                                // 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: "{extension_id}" }}
                                }}));
                                
                                // Make chrome.runtime available globally
                                if (window.runtimeBridge) {{
                                    console.log("RuntimeBridge is available, initializing chrome.runtime API for content script");
                                    // 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 = "{extension_id}";
                                    chrome.runtime.sendMessage = function(extensionId, message, options, callback) {{
                                        if (typeof extensionId !== 'string') {{
                                            callback = options;
                                            options = message;
                                            message = extensionId;
                                            extensionId = "{extension_id}";
                                        }}
                                        // 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));
                                    }};
                                }}
                            }});
                            return true;
                        }} catch (e) {{
                            console.error("Error initializing QWebChannel in content script:", e);
                            return false;
                        }}
                    }} else {{
                        console.log("qt.webChannelTransport not available yet in content script, will retry later");
                        return false;
                    }}
                }}
                
                // Create a global variable to track initialization attempts
                window.__qwebchannel_init_attempts = 0;
                
                // Try to initialize immediately
                if (!initializeWebChannel()) {{
                    // If it fails, set up a retry mechanism
                    const MAX_RETRIES = 50;
                    
                    const retryInterval = setInterval(function() {{
                        window.__qwebchannel_init_attempts++;
                        console.log("Retrying QWebChannel initialization in content script... (attempt " + window.__qwebchannel_init_attempts + " of " + MAX_RETRIES + ")");
                        
                        // Check if qt object exists, if not, try to create it
                        if (typeof qt === 'undefined') {{
                            console.log("qt object is undefined in content script, creating placeholder");
                            window.qt = {{}};
                        }}
                        
                        if (initializeWebChannel() || window.__qwebchannel_init_attempts >= MAX_RETRIES) {{
                            clearInterval(retryInterval);
                            if (window.__qwebchannel_init_attempts >= MAX_RETRIES) {{
                                console.error("Failed to initialize QWebChannel in content script after " + MAX_RETRIES + " attempts");
                            }}
                        }}
                    }}, 200);
                }}
            }})();
            """
        
        # Add code to load each content script
        for js_file in js_files:
            script_url = f"qextension://{extension_id}/{js_file}"
            injection_script += f"""
            (function() {{
                console.log("Injecting content script: {script_url}");
                
                // Create a script element to load the content script
                var script = document.createElement('script');
                script.src = "{script_url}";
                script.type = "text/javascript";
                script.async = false;
                
                // Add error handling
                script.onerror = function() {{
                    console.error("Failed to load content script: {script_url}");
                }};
                
                // Add the script to the page
                (document.head || document.documentElement).appendChild(script);
            }})();
            """
        
        return injection_script


