#!/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 sys
import subprocess
import tempfile
import json
import shutil
import asyncio
import platform
import signal
from pathlib import Path
from typing import Dict, List, Optional, Union, Callable, Any
import configparser

from PyQt6.QtCore import Qt, QUrl, QProcess, pyqtSignal, QSize, QRect, QTimer, QMimeData, QPoint, QByteArray
from PyQt6.QtGui import QIcon, QAction, QKeySequence, QFont, QColor, QPainter, QDrag, QPixmap
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QTabWidget, QToolBar, QLineEdit,
    QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QDialog,
    QLabel, QListWidget, QListWidgetItem, QCheckBox, QMenu,
    QSizePolicy, QStyle, QFrame, QSplitter, QMessageBox, QStatusBar, QTabBar, QStyleOptionTab, QFileDialog,
    QInputDialog
)
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebEngineCore import QWebEngineProfile, QWebEngineSettings, QWebEnginePage

# Import Playwright for API compatibility
from playwright.async_api import async_playwright, Browser as PlaywrightBrowser
from playwright.async_api import Page as PlaywrightPage
from playwright.async_api import BrowserContext as PlaywrightBrowserContext


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
        page = QWebEnginePage(self.profile, self.web_view)
        self.web_view.setPage(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}")
            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 on_load_started(self):
        self.is_loading = True
        self.loadStatusChanged.emit()

    def on_load_finished(self, success):
        """Handle the page load finishing."""
        self.is_loading = False
        self.loadStatusChanged.emit()
        if success:
            print(f"Page loaded successfully: {self.web_view.url().toString()}")
            # Update the title if available
            title = self.web_view.title()
            if title:
                self.titleChanged.emit(title)
        else:
            print(f"Failed to load page: {self.url}")
    
    # 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()


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]
        # This is the actual path on disk, e.g., /path/to/ext or /path/to/ext.disabled
        ext_path = item.data(Qt.ItemDataRole.UserRole)
        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()
        web_view.load(QUrl.fromLocalFile(os.path.abspath(popup_file)))
        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 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 ProfileDialog(QDialog):
    """
    Dialog for selecting or creating a browser profile.
    """
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Select Profile")
        self.setMinimumWidth(400)
        self.selected_profile_path = None
        self.profiles_dir = "data"
        
        if not os.path.exists(self.profiles_dir):
            os.makedirs(self.profiles_dir)

        layout = QVBoxLayout(self)
        
        layout.addWidget(QLabel("Select a profile to launch:"))
        
        self.profile_list = QListWidget()
        self.profile_list.itemDoubleClicked.connect(self.accept_selection)
        layout.addWidget(self.profile_list)
        
        button_layout = QHBoxLayout()
        
        self.new_button = QPushButton("Create New")
        self.new_button.clicked.connect(self.create_new_profile)
        button_layout.addWidget(self.new_button)

        self.delete_button = QPushButton("Delete")
        self.delete_button.clicked.connect(self.delete_profile)
        button_layout.addWidget(self.delete_button)

        button_layout.addStretch()

        self.select_button = QPushButton("Launch")
        self.select_button.setDefault(True)
        self.select_button.clicked.connect(self.accept_selection)
        button_layout.addWidget(self.select_button)
        
        layout.addLayout(button_layout)
        
        self.load_profiles()
        self.update_button_states()

    def update_button_states(self):
        has_selection = len(self.profile_list.selectedItems()) > 0
        self.select_button.setEnabled(has_selection)
        self.delete_button.setEnabled(has_selection)

    def load_profiles(self):
        self.profile_list.clear()
        profiles = []
        for item in os.listdir(self.profiles_dir):
            if os.path.isdir(os.path.join(self.profiles_dir, item)) and item.startswith("browser_profile_"):
                profiles.append(item)
        
        for profile_dir_name in sorted(profiles):
            profile_name = profile_dir_name.replace("browser_profile_", "")
            list_item = QListWidgetItem(profile_name)
            list_item.setData(Qt.ItemDataRole.UserRole, os.path.join(self.profiles_dir, profile_dir_name))
            self.profile_list.addItem(list_item)
        
        if self.profile_list.count() > 0:
            self.profile_list.setCurrentRow(0)
        
        self.profile_list.itemSelectionChanged.connect(self.update_button_states)
        self.update_button_states()

    def create_new_profile(self):
        while True:
            profile_name, ok = QInputDialog.getText(self, "Create New Profile", "Enter a name for the new profile:")
            if not ok:
                return  # User cancelled
            
            if not profile_name.strip():
                QMessageBox.warning(self, "Invalid Name", "Profile name cannot be empty.")
                continue

            profile_path = os.path.join(self.profiles_dir, f"browser_profile_{profile_name.strip()}")
            if not os.path.exists(profile_path):
                try:
                    os.makedirs(profile_path)
                    self._install_default_extensions(profile_path)
                    self.load_profiles()
                    # Select the new profile
                    for i in range(self.profile_list.count()):
                        if self.profile_list.item(i).text() == profile_name.strip():
                            self.profile_list.setCurrentRow(i)
                            break
                    return
                except OSError as e:
                    QMessageBox.critical(self, "Error", f"Could not create profile directory: {e}")
                    return
            else:
                QMessageBox.warning(self, "Profile Exists", "A profile with that name already exists.")

    def delete_profile(self):
        selected_items = self.profile_list.selectedItems()
        if not selected_items:
            return
        
        item = selected_items[0]
        profile_name = item.text()
        profile_path = item.data(Qt.ItemDataRole.UserRole)

        reply = QMessageBox.question(self, "Confirm Deletion", f"Are you sure you want to delete the profile '{profile_name}'?\nThis will permanently delete all its data.",
                                     QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No)

        if reply == QMessageBox.StandardButton.Yes:
            try:
                shutil.rmtree(profile_path)
                self.load_profiles()
                self.update_button_states()
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Could not delete profile: {e}")

    def accept_selection(self):
        selected_items = self.profile_list.selectedItems()
        if not selected_items:
            return
        
        item = selected_items[0]
        self.selected_profile_path = item.data(Qt.ItemDataRole.UserRole)
        self.accept()

    @staticmethod
    def _install_default_extensions(profile_path):
        """Copies default extensions from assets to a new profile directory."""
        source_extensions_dir = "assets/browser/extensions"
        if not os.path.isdir(source_extensions_dir):
            print(f"Default extensions directory not found: {source_extensions_dir}")
            return

        dest_extensions_dir = os.path.join(profile_path, "Default", "Extensions")
        
        try:
            os.makedirs(dest_extensions_dir, exist_ok=True)
            
            for item_name in os.listdir(source_extensions_dir):
                source_item_path = os.path.join(source_extensions_dir, item_name)
                dest_item_path = os.path.join(dest_extensions_dir, item_name)
                
                if os.path.isdir(source_item_path):
                    if not os.path.exists(dest_item_path):
                        shutil.copytree(source_item_path, dest_item_path)
                        print(f"Installed default extension '{item_name}' to profile.")
        except (OSError, IOError) as e:
            QMessageBox.warning(None, "Extension Installation Error",
                                f"Could not install default extensions: {e}")

    @staticmethod
    def get_profile_path(parent=None):
        profiles_dir = "data"
        if not os.path.exists(profiles_dir):
            os.makedirs(profiles_dir)
            
        profiles = [p for p in os.listdir(profiles_dir) if p.startswith("browser_profile_")]

        if not profiles:
            # First run experience: must create a profile to continue.
            while True:
                profile_name, ok = QInputDialog.getText(parent, "Create First Profile", "Welcome! Please create a profile to begin:")
                if not ok:
                    return None  # User cancelled initial creation, so exit.
                
                if not profile_name.strip():
                    QMessageBox.warning(parent, "Invalid Name", "Profile name cannot be empty.")
                    continue

                profile_path = os.path.join(profiles_dir, f"browser_profile_{profile_name.strip()}")
                
                if os.path.exists(profile_path):
                    QMessageBox.warning(parent, "Profile Exists", "A profile with that name already exists. Please choose another name.")
                    continue

                try:
                    os.makedirs(profile_path)
                    ProfileDialog._install_default_extensions(profile_path)
                    return profile_path  # Success!
                except OSError as e:
                    QMessageBox.critical(parent, "Error", f"Could not create profile directory: {e}")
                    return None
        else:
            # Profiles exist, show the manager dialog.
            dialog = ProfileDialog(parent)
            if dialog.exec() == QDialog.DialogCode.Accepted:
                return dialog.selected_profile_path
            else:
                return None  # User closed the manager.


class Browser(QMainWindow):
    """
    Main browser window that wraps the Playwright browser class.
    """
    instances = []

    def __init__(self, initial_url=None, debug=False, detached_tab=None, profile_path=None):
        super().__init__()
        Browser.instances.append(self)
        self.setWindowTitle("SHMCamStudio Browser")
        self.setWindowIcon(QIcon('assets/logo.jpg'))
        self.resize(1024, 768)

        # Create a central widget and layout
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        self.layout = QVBoxLayout(self.central_widget)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)

        # Create toolbar
        self.toolbar = QToolBar()
        self.toolbar.setMovable(False)
        self.toolbar.setIconSize(QSize(24, 24))

        # Add back button
        self.back_button = QPushButton()
        self.back_button.setIcon(self._get_themed_icon(QStyle.StandardPixmap.SP_ArrowBack))
        self.back_button.setToolTip("Back")
        self.back_button.clicked.connect(self.navigate_back)
        self.toolbar.addWidget(self.back_button)

        # Add forward button
        self.forward_button = QPushButton()
        self.forward_button.setIcon(self._get_themed_icon(QStyle.StandardPixmap.SP_ArrowForward))
        self.forward_button.setToolTip("Forward")
        self.forward_button.clicked.connect(self.navigate_forward)
        self.toolbar.addWidget(self.forward_button)

        # Add reload button
        self.reload_button = QPushButton()
        self.reload_button.setIcon(self._get_themed_icon(QStyle.StandardPixmap.SP_BrowserReload))
        self.reload_button.setToolTip("Reload (F5)")
        self.reload_button.clicked.connect(self.reload_page)
        self.toolbar.addWidget(self.reload_button)

        # Add home button
        self.home_button = QPushButton()
        self.home_button.setIcon(self._get_themed_icon(QStyle.StandardPixmap.SP_DirHomeIcon))
        self.home_button.setToolTip("Home")
        self.home_button.clicked.connect(self.navigate_home)
        self.toolbar.addWidget(self.home_button)

        # Add URL input
        self.url_input = QLineEdit()
        self.url_input.setPlaceholderText("Enter URL...")
        self.url_input.returnPressed.connect(self.navigate_to_url)
        self.toolbar.addWidget(self.url_input)

        # Add extensions button
        self.extensions_button = QPushButton()
        self.extensions_button.setIcon(self._get_themed_icon(QStyle.StandardPixmap.SP_FileDialogDetailedView))
        self.extensions_button.setToolTip("Extensions")
        self.extensions_button.clicked.connect(self.show_extensions)
        self.toolbar.addWidget(self.extensions_button)

        # Add new window button
        self.new_window_button = QPushButton()
        self.new_window_button.setIcon(self._get_themed_icon(QStyle.StandardPixmap.SP_FileIcon))
        self.new_window_button.setToolTip("New Window (Ctrl+W)")
        self.new_window_button.clicked.connect(self.open_new_window)
        self.toolbar.addWidget(self.new_window_button)

        if debug:
            self.debug_button = QPushButton()
            self.debug_button.setIcon(self._get_themed_icon(QStyle.StandardPixmap.SP_MessageBoxWarning))
            self.debug_button.setToolTip("Toggle Developer Tools")
            self.debug_button.clicked.connect(self.toggle_devtools)
            self.toolbar.addWidget(self.debug_button)

        self.layout.addWidget(self.toolbar)

        # Create tab widget
        self.tabs = QTabWidget()
        self.tab_bar = DetachableTabBar()
        self.tabs.setTabBar(self.tab_bar)
        self.tab_bar.tabDetached.connect(self.handle_tab_detached)
        
        self.tabs.setTabsClosable(True)
        # self.tabs.setMovable(True) # This is handled by our DetachableTabBar
        self.tabs.setDocumentMode(True)
        self.tabs.tabCloseRequested.connect(self.close_tab)
        self.tabs.currentChanged.connect(self.tab_changed)

        self.layout.addWidget(self.tabs)

        # Add status bar
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)

        # Add a new tab button
        self.tabs.setCornerWidget(self._create_new_tab_button(), Qt.Corner.TopRightCorner)

        # Set the home URL from config
        config = configparser.ConfigParser()
        config.read('shmcamstudio.conf')
        self.home_url = config.get('Browser', 'home_url', fallback='https://www.google.com')

        # Centralized browser profile
        if not profile_path:
            # This should not happen if main is used, but as a fallback
            profile_path = "data/browser_profile_default"
        self.profile_dir = profile_path
        if not os.path.exists(self.profile_dir):
            os.makedirs(self.profile_dir)
        
        # Extensions must be in a specific subdirectory of the profile
        self.extensions_dir = os.path.join(self.profile_dir, "Default", "Extensions")
        if not os.path.exists(self.extensions_dir):
            os.makedirs(self.extensions_dir)
            
        self.profile = QWebEngineProfile(self.profile_dir, self)
        
        if detached_tab:
            widget, title = detached_tab
            index = self.tabs.addTab(widget, title)
            self.tabs.setCurrentIndex(index)
            widget.browser = self
            widget.urlChanged.connect(self.update_url)
            widget.titleChanged.connect(lambda t, tab=widget: self.update_title(t, tab))
            widget.loadStatusChanged.connect(self.update_status_bar)
            if hasattr(widget, 'web_view'):
                self.update_title(widget.web_view.title(), widget)
                self.update_url(widget.web_view.url().toString())
        else:
            # Create a new tab on startup
            url_to_open = initial_url if initial_url is not None else self.home_url
            self.new_page(url_to_open)

        # Set up keyboard shortcuts
        self._setup_shortcuts()

        # Store Playwright objects
        self.playwright = None
        self.playwright_browser = None
        self.browser_contexts = {}
        self.dev_tools_windows = []

    
    def closeEvent(self, event):
        if self in Browser.instances:
            Browser.instances.remove(self)
        super().closeEvent(event)
    
    def _get_themed_icon(self, standard_pixmap, color="white"):
        """Create a themed icon from a standard pixmap."""
        icon = self.style().standardIcon(standard_pixmap)
        pixmap = icon.pixmap(self.toolbar.iconSize())
        painter = QPainter(pixmap)
        painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
        painter.fillRect(pixmap.rect(), QColor(color))
        painter.end()
        return QIcon(pixmap)
    
    def _create_new_tab_button(self):
        """Create a button for adding new tabs."""
        button = QPushButton("+")
        button.setToolTip("New Tab")
        button.clicked.connect(lambda: self.new_page(self.home_url))
        return button
    
    def _setup_shortcuts(self):
        """Set up keyboard shortcuts."""
        # Ctrl+T: New Tab
        new_tab_shortcut = QAction("New Tab", self)
        new_tab_shortcut.setShortcut(QKeySequence("Ctrl+T"))
        new_tab_shortcut.triggered.connect(lambda: self.new_page(self.home_url))
        self.addAction(new_tab_shortcut)

        # Ctrl+Tab: Cycle Tabs
        cycle_tabs_shortcut = QAction("Cycle Tabs", self)
        cycle_tabs_shortcut.setShortcut(QKeySequence("Ctrl+Tab"))
        cycle_tabs_shortcut.triggered.connect(self.cycle_tabs)
        self.addAction(cycle_tabs_shortcut)

        # Ctrl+W: New Window
        new_window_shortcut = QAction("New Window", self)
        new_window_shortcut.setShortcut(QKeySequence("Ctrl+W"))
        new_window_shortcut.triggered.connect(self.open_new_window)
        self.addAction(new_window_shortcut)

        # Ctrl+L: Focus URL bar
        focus_url_shortcut = QAction("Focus URL", self)
        focus_url_shortcut.setShortcut(QKeySequence("Ctrl+L"))
        focus_url_shortcut.triggered.connect(self.url_input.setFocus)
        self.addAction(focus_url_shortcut)

        # F5: Reload page
        reload_shortcut = QAction("Reload", self)
        reload_shortcut.setShortcut(QKeySequence("F5"))
        reload_shortcut.triggered.connect(self.reload_page)
        self.addAction(reload_shortcut)
    
    def cycle_tabs(self):
        """Cycle through the open tabs."""
        if self.tabs.count() > 1:
            next_index = (self.tabs.currentIndex() + 1) % self.tabs.count()
            self.tabs.setCurrentIndex(next_index)

    def on_tab_removed(self):
        """Close the window if the last tab is removed."""
        if self.tabs.count() == 0:
            self.close()

    def handle_tab_detached(self, index, pos):
        """Handle a tab being detached to a new window."""
        if index < 0 or index >= self.tabs.count():
            return

        widget = self.tabs.widget(index)
        title = self.tabs.tabText(index)

        try:
            widget.urlChanged.disconnect(self.update_url)
            widget.titleChanged.disconnect()
            widget.loadStatusChanged.disconnect(self.update_status_bar)
        except (TypeError, RuntimeError):
            pass

        # Create a new browser instance, which will take ownership of the widget.
        new_browser = Browser(
            debug=("--debug" in sys.argv),
            detached_tab=(widget, title),
            profile_path=self.profile_dir
        )
        
        new_browser.setGeometry(self.geometry())
        new_browser.move(pos - QPoint(new_browser.width() // 4, 10))
        new_browser.show()

        # Now that the widget is safely reparented, remove the tab from the old browser.
        # The on_tab_removed method will handle closing the window if it's empty.
        self.tabs.removeTab(index)
        self.on_tab_removed()

    def new_page(self, url="about:blank"):
        """Create a new browser tab with the specified URL."""
        # Create a new tab
        tab = BrowserTab(parent=self.tabs, url=url, profile=self.profile)
        # Store a reference to the Browser instance
        tab.browser = self
        tab.urlChanged.connect(self.update_url)
        tab.titleChanged.connect(lambda title, tab=tab: self.update_title(title, tab))
        tab.loadStatusChanged.connect(self.update_status_bar)
        
        # Add the tab to the tab widget
        index = self.tabs.addTab(tab, "New Tab")
        self.tabs.setCurrentIndex(index)
        
        return tab
    
    def close_tab(self, index):
        """Close the tab at the specified index."""
        if index < 0 or index >= self.tabs.count():
            return
        
        # Get the tab widget and schedule it for deletion.
        tab = self.tabs.widget(index)
        if tab:
            tab.deleteLater()
        
        # Remove the tab from the tab bar.
        self.tabs.removeTab(index)
        self.on_tab_removed()
    
    def tab_changed(self, index):
        """Handle tab change events."""
        if index < 0:
            return
        
        # Update the URL input
        tab = self.tabs.widget(index)
        if hasattr(tab, 'url'):
            self.url_input.setText(tab.url)
        self.update_status_bar()
    
    def update_url(self, url):
        """Update the URL input when the page URL changes."""
        self.url_input.setText(url)
        
        # Update the tab's stored URL
        current_tab = self.tabs.currentWidget()
        if current_tab:
            current_tab.url = url
    
    def update_title(self, title, tab):
        """Update the tab title when the page title changes."""
        index = self.tabs.indexOf(tab)
        if index >= 0:
            self.tabs.setTabText(index, title or "New Tab")
    
    def navigate_to_url(self):
        """Navigate to the URL in the URL input."""
        url = self.url_input.text().strip()
        
        # Add http:// if no protocol is specified
        if url and not url.startswith(("http://", "https://", "file://", "about:")):
            url = "http://" + url
        
        # Navigate the current tab
        current_tab = self.tabs.currentWidget()
        if current_tab:
            current_tab.navigate(url)
    
    def navigate_home(self):
        """Navigate to the home URL."""
        current_tab = self.tabs.currentWidget()
        if current_tab:
            current_tab.navigate(self.home_url)

    def navigate_back(self):
        """Navigate back in the current tab."""
        current_tab = self.tabs.currentWidget()
        if current_tab and hasattr(current_tab, 'web_view'):
            current_tab.web_view.back()

    def navigate_forward(self):
        """Navigate forward in the current tab."""
        current_tab = self.tabs.currentWidget()
        if current_tab and hasattr(current_tab, 'web_view'):
            current_tab.web_view.forward()

    def reload_page(self):
        """Reload the current tab."""
        current_tab = self.tabs.currentWidget()
        if current_tab and hasattr(current_tab, 'web_view'):
            current_tab.web_view.reload()
    
    def open_new_window(self):
        """Open a new browser window."""
        new_browser = Browser(debug="--debug" in sys.argv, profile_path=self.profile_dir)
        new_browser.show()
    
    def toggle_devtools(self):
        """Opens the remote developer tools URL in a new window."""
        dev_tools_window = QDialog(self)
        dev_tools_window.setWindowTitle("Developer Tools")
        dev_tools_window.resize(1200, 800)
        
        layout = QVBoxLayout()
        web_view = QWebEngineView()
        web_view.load(QUrl("http://localhost:9222"))
        layout.addWidget(web_view)
        
        dev_tools_window.setLayout(layout)
        dev_tools_window.show()
        
        self.dev_tools_windows.append(dev_tools_window)
        dev_tools_window.finished.connect(lambda: self.dev_tools_windows.remove(dev_tools_window))

    def update_status_bar(self):
        """Update the status bar based on the current tab's state."""
        current_tab = self.tabs.currentWidget()
        if not current_tab:
            self.status_bar.showMessage("Ready")
            return
        
        if current_tab.is_loading:
            self.status_bar.showMessage("Loading...")
        else:
            self.status_bar.showMessage("Ready")

    def reload_profile_and_tabs(self):
        """Reloads the browser profile and recreates pages in all tabs."""
        print("Reloading profile to apply extension changes...")

        # 1. Save the URLs and current index of all open tabs
        urls = []
        current_index = self.tabs.currentIndex()
        for i in range(self.tabs.count()):
            tab = self.tabs.widget(i)
            if hasattr(tab, 'web_view') and tab.web_view.url().isValid():
                urls.append(tab.web_view.url().toString())
            else:
                urls.append("about:blank")

        # 2. Store the old profile to delete it later
        old_profile = self.profile
        old_profile.clearHttpCache()

        # 3. Clear all existing tabs. This removes references to pages using the old profile.
        self.tabs.clear()

        # 4. Create a new profile object. This forces a re-read from disk because
        #    the old one will be properly deleted.
        self.profile = QWebEngineProfile(self.profile_dir, self)
        
        # 5. Re-create all tabs with the new profile
        if not urls: # Ensure there's at least one tab
            urls.append(self.home_url)
            
        for url in urls:
            self.new_page(url)
        
        # 6. Restore the previously active tab
        if current_index != -1 and current_index < len(urls):
            self.tabs.setCurrentIndex(current_index)
            
        # 7. Schedule the old profile for deletion. This is crucial.
        old_profile.setParent(None)
        old_profile.deleteLater()

        print("Profile and tabs reloaded.")

    def show_extensions(self):
        """Show the extensions dialog."""
        dialog = ExtensionDialog(self, self.extensions_dir)
        dialog.exec()


class QPlaywrightBrowser:
    """
    A class that wraps the Playwright browser API and uses Qt6Browser internally.
    This class exposes the Playwright API while using the Qt6 browser for display.
    """
    def __init__(self):
        self.app = QApplication.instance() or QApplication(sys.argv)
        self.browser_ui = None
        self.playwright = None
        self.browser = None
        self.contexts = {}
        self.event_loop = None
    
    async def launch(self, **kwargs):
        """Launch a new browser instance."""
        # Initialize Playwright if not already done
        if not self.playwright:
            self.playwright = await async_playwright().start()
        
        # Create the Qt browser UI
        if not self.browser_ui:
            self.browser_ui = Browser()
            self.browser_ui.show()
        
        # Launch the actual Playwright browser (hidden)
        # We'll use this for API compatibility but display in Qt
        self.browser = await self.playwright.chromium.launch(
            headless=True,  # Run headless since we're displaying in Qt
            **kwargs
        )
        
        # Create a default context
        default_context = await self.browser.new_context()
        self.contexts["default"] = default_context
        
        return self
    
    async def new_context(self, **kwargs):
        """Create a new browser context."""
        if not self.browser:
            await self.launch()
        
        # Create a new context in the Playwright browser
        context_id = f"context_{len(self.contexts)}"
        context = await self.browser.new_context(**kwargs)
        self.contexts[context_id] = context
        
        # Return a wrapper that provides both Playwright API and Qt UI
        return QPlaywrightBrowserContext(self, context, context_id)
    
    async def new_page(self, url="about:blank"):
        """Create a new page in the default context."""
        if "default" not in self.contexts:
            await self.launch()
        
        # Create a new page in the Playwright browser
        pw_page = await self.contexts["default"].new_page()
        await pw_page.goto(url)
        
        # Create a new tab in the Qt browser
        qt_tab = self.browser_ui.new_page(url)
        qt_tab.page = pw_page
        
        # Return a wrapper that provides both Playwright API and Qt UI
        return QPlaywrightPage(pw_page, qt_tab)
    
    async def close(self):
        """Close the browser."""
        # Close all Playwright contexts and browser
        if self.browser:
            for context_id, context in self.contexts.items():
                await context.close()
            await self.browser.close()
            self.contexts = {}
            self.browser = None
        
        # Close the Qt browser UI
        if self.browser_ui:
            self.browser_ui.close()
            self.browser_ui = None
        
        # Close Playwright
        if self.playwright:
            await self.playwright.stop()
            self.playwright = None
    
    def run(self):
        """Run the application event loop."""
        return self.app.exec()
    
    def run_async(self, coro):
        """Run an async coroutine in the Qt event loop."""
        if not self.event_loop:
            self.event_loop = asyncio.new_event_loop()
            asyncio.set_event_loop(self.event_loop)
        
        # Create a QTimer to process asyncio events
        timer = QTimer()
        timer.timeout.connect(lambda: self.event_loop.run_until_complete(asyncio.sleep(0)))
        timer.start(10)  # 10ms interval
        
        # Run the coroutine
        future = asyncio.run_coroutine_threadsafe(coro, self.event_loop)
        return future


class QPlaywrightBrowserContext:
    """
    A wrapper around a Playwright BrowserContext that provides both
    Playwright API and Qt UI integration.
    """
    def __init__(self, qbrowser, context, context_id):
        self.qbrowser = qbrowser
        self.context = context
        self.context_id = context_id
        self.pages = []
    
    async def new_page(self):
        """Create a new page in this context."""
        # Create a new page in the Playwright context
        pw_page = await self.context.new_page()
        
        # Create a new tab in the Qt browser
        qt_tab = self.qbrowser.browser_ui.new_page("about:blank")
        qt_tab.page = pw_page
        
        # Create a wrapper page
        page = QPlaywrightPage(pw_page, qt_tab)
        self.pages.append(page)
        
        return page
    
    async def close(self):
        """Close this context."""
        # Close all pages
        for page in self.pages:
            await page.close()
        
        # Close the Playwright context
        await self.context.close()
        
        # Remove from the qbrowser contexts
        if self.context_id in self.qbrowser.contexts:
            del self.qbrowser.contexts[self.context_id]


class QPlaywrightPage:
    """
    A wrapper around a Playwright Page that provides both
    Playwright API and Qt UI integration.
    """
    def __init__(self, pw_page, qt_tab):
        self.pw_page = pw_page
        self.qt_tab = qt_tab
        
        # Forward Playwright page methods and properties
        self.goto = pw_page.goto
        self.click = pw_page.click
        self.fill = pw_page.fill
        self.type = pw_page.type
        self.press = pw_page.press
        self.wait_for_selector = pw_page.wait_for_selector
        self.wait_for_navigation = pw_page.wait_for_navigation
        self.wait_for_load_state = pw_page.wait_for_load_state
        self.evaluate = pw_page.evaluate
        self.screenshot = pw_page.screenshot
        self.content = pw_page.content
        self.title = pw_page.title
        self.url = pw_page.url
    
    async def close(self):
        """Close this page."""
        # Close the Playwright page
        await self.pw_page.close()
        
        # Close the Qt tab
        # We need to use the Qt event loop for this
        index = self.qt_tab.parent.indexOf(self.qt_tab)
        if index >= 0:
            self.qt_tab.parent.close_tab(index)


async def main_async():
    """Async main function to run the browser."""
    browser = QPlaywrightBrowser()
    await browser.launch()
    
    # Open a page
    url = "https://www.google.com"
    if len(sys.argv) > 1:
        url = sys.argv[1]
    
    await browser.new_page(url)
    
    # Keep the browser running
    while True:
        await asyncio.sleep(0.1)


def main():
    """Main function to run the browser as a standalone application."""
    debug_mode = "--debug" in sys.argv
    if debug_mode:
        os.environ["QTWEBENGINE_REMOTE_DEBUGGING"] = "9222"
        
    app = QApplication.instance() or QApplication(sys.argv)
    
    # Allow Ctrl+C to kill the application gracefully
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    app.setWindowIcon(QIcon('assets/logo.jpg'))

    # Set a modern, dark theme
    app.setStyle("Fusion")
    dark_palette = app.palette()
    dark_palette.setColor(dark_palette.ColorRole.Window, QColor(45, 45, 45))
    dark_palette.setColor(dark_palette.ColorRole.WindowText, Qt.GlobalColor.white)
    dark_palette.setColor(dark_palette.ColorRole.Base, QColor(25, 25, 25))
    dark_palette.setColor(dark_palette.ColorRole.AlternateBase, QColor(53, 53, 53))
    dark_palette.setColor(dark_palette.ColorRole.ToolTipBase, QColor(25, 25, 25))
    dark_palette.setColor(dark_palette.ColorRole.ToolTipText, Qt.GlobalColor.white)
    dark_palette.setColor(dark_palette.ColorRole.Text, Qt.GlobalColor.white)
    dark_palette.setColor(dark_palette.ColorRole.Button, QColor(53, 53, 53))
    dark_palette.setColor(dark_palette.ColorRole.ButtonText, Qt.GlobalColor.white)
    dark_palette.setColor(dark_palette.ColorRole.BrightText, Qt.GlobalColor.red)
    dark_palette.setColor(dark_palette.ColorRole.Link, QColor(42, 130, 218))
    dark_palette.setColor(dark_palette.ColorRole.Highlight, QColor(42, 130, 218))
    dark_palette.setColor(dark_palette.ColorRole.HighlightedText, Qt.GlobalColor.black)
    app.setPalette(dark_palette)

    # Set a better font
    font = QFont("Cantarell", 10)
    if platform.system() == "Windows":
        font = QFont("Segoe UI", 10)
    elif platform.system() == "Darwin":
        font = QFont("San Francisco", 10)
    app.setFont(font)

    # Create a dummy parent for dialogs to ensure they are properly modal
    dummy_parent = QWidget()

    # Get profile path
    profile_path = ProfileDialog.get_profile_path(dummy_parent)
    if not profile_path:
        return 0 # Exit gracefully

    # Create a simple browser directly without Playwright for now
    # This ensures we at least get a window showing
    initial_url = None
    if len(sys.argv) > 1 and not sys.argv[1].startswith('--'):
        initial_url = sys.argv[1]

    browser = Browser(initial_url=initial_url, debug=debug_mode, profile_path=profile_path)
    browser.show()
    
    print("Browser window should be visible now")
    
    # Run the Qt event loop
    return app.exec()


if __name__ == "__main__":
    sys.exit(main())
