
from __future__ import annotations
import json
from pathlib import Path

from PySide6.QtCore import QObject, Qt, Signal
from PySide6.QtGui import QAction, QIcon
from PySide6.QtWidgets import (
    QApplication,
    QCheckBox,
    QDialog,
    QFileDialog,
    QFormLayout,
    QGroupBox,
    QHBoxLayout,
    QLineEdit,
    QListWidget,
    QMainWindow,
    QMenu,
    QMessageBox,
    QPlainTextEdit,
    QPushButton,
    QSplitter,
    QSystemTrayIcon,
    QTableView,
    QTabWidget,
    QVBoxLayout,
    QWidget,
    QInputDialog,
)

from models import (
    AddonModel,
    NetworkTableModel,
    PluginCrashTableModel,
    PluginLogTableModel,
    PluginTableModel,
    ProfileContext,
)
from addons import AddonManager
from network import ProxyConfig
from config import (
    PLUGIN_ALLOWED_EXTENSIONS,
    PLUGIN_BLOCKED_PATTERNS,
    PLUGIN_FILE_PATTERN,
    PLUGIN_MAX_SIZE,
    profile_extension_dir,
    profile_plugin_dir,
    profile_theme_dir,
    profile_root,
)

# v2
class ContextManager:
    def __init__(
        self,
        db,
        settings: dict,
    ):
        self.db = db
        self.settings = settings
        self._contexts: dict[
            str,
            ProfileContext,
        ] = {}

    def current(
        self,
    ) -> ProfileContext:
        return self.profile(
            self.settings.get(
                "active_profile",
                "default",
            )
        )

    def get(
        self,
        profile_name: str,
    ) -> ProfileContext:
        return self.profile(
            profile_name
        )

    def register(
        self,
        context: ProfileContext,
    ) -> None:
        self._contexts[
            context.name
        ] = context

    def remove(
        self,
        profile_name: str,
    ) -> None:
        self._contexts.pop(
            profile_name,
            None,
        )

    def profile(
        self,
        profile_name: str,
    ) -> ProfileContext:

        if (
            profile_name
            in self._contexts
        ):
            return self._contexts[
                profile_name
            ]

        self.db.ensure_profile(
            profile_name
        )

        root = profile_root(
            profile_name
        )

        # v3
        context = ProfileContext(
            name=profile_name,
            root=root,
            storage_path=(
                root / "storage"
            ),
            cache_path=(
                root / "cache"
            ),
            downloads_path=(
                root / "downloads"
            ),
            plugins_path=(
                profile_plugin_dir(
                    profile_name
                )
            ),
            themes_path=(
                profile_theme_dir(
                    profile_name
                )
            ),
            extensions_path=(
                profile_extension_dir(
                    profile_name
                )
            ),
            profile_db=(
                root / "profile.db"
            ),
            permissions_db=(
                root / "permissions.db"
            ),
            network_db=(
                root / "network.db"
            ),
        )
        context.storage_path.mkdir(
            parents=True,
            exist_ok=True,
        )

        context.cache_path.mkdir(
            parents=True,
            exist_ok=True,
        )

        context.downloads_path.mkdir(
            parents=True,
            exist_ok=True,
        )

        self._contexts[
            profile_name
        ] = context

        return context

    def switch(
        self,
        profile_name: str,
    ) -> ProfileContext:

        self.settings[
            "active_profile"
        ] = profile_name
        save_settings(
            self.settings
        )
        return self.profile(
            profile_name
        )
class PluginManagerWindow(
    QDialog
):
    def __init__(
        self,
        browser,
        db,
        profile_name,
        parent=None,
    ):
        super().__init__(
            parent
        )

        self.browser = browser
        self.db = db
        self.profile_name = (
            profile_name
        )

        self.resize(
            1200,
            850,
        )

        self.setWindowTitle(
            "Plugins"
        )

        self.model = (
            PluginTableModel()
        )

        self.logs_model = (
            PluginLogTableModel()
        )

        self.crash_model = (
            PluginCrashTableModel()
        )

        self.plugins_table = (
            QTableView()
        )

        self.logs_table = (
            QTableView()
        )

        self.crashes_table = (
            QTableView()
        )

        self.diagnostics = (
            QPlainTextEdit()
        )

        self.diagnostics.setReadOnly(
            True
        )

        self.plugins_table.setModel(
            self.model
        )

        self.logs_table.setModel(
            self.logs_model
        )

        self.crashes_table.setModel(
            self.crash_model
        )

        tabs = QTabWidget()

        tabs.addTab(
            self.plugins_table,
            "Plugins",
        )

        tabs.addTab(
            self.logs_table,
            "Logs",
        )

        tabs.addTab(
            self.crashes_table,
            "Crashes",
        )

        tabs.addTab(
            self.diagnostics,
            "Diagnostics",
        )

        self.install_file_btn = (
            QPushButton(
                "Install File"
            )
        )

        self.install_url_btn = (
            QPushButton(
                "Install URL"
            )
        )

        self.enable_btn = (
            QPushButton(
                "Enable"
            )
        )

        self.disable_btn = (
            QPushButton(
                "Disable"
            )
        )

        self.reload_btn = (
            QPushButton(
                "Reload"
            )
        )

        self.delete_btn = (
            QPushButton(
                "Delete"
            )
        )

        buttons = QHBoxLayout()

        for button in (
            self.install_file_btn,
            self.install_url_btn,
            self.enable_btn,
            self.disable_btn,
            self.reload_btn,
            self.delete_btn,
        ):
            buttons.addWidget(
                button
            )

        layout = QVBoxLayout(
            self
        )

        layout.addWidget(
            tabs
        )

        layout.addLayout(
            buttons
        )

        self.install_file_btn.clicked.connect(
            self.install_file
        )

        self.install_url_btn.clicked.connect(
            self.install_url
        )

        self.enable_btn.clicked.connect(
            self.enable_plugin
        )

        self.disable_btn.clicked.connect(
            self.disable_plugin
        )

        self.reload_btn.clicked.connect(
            self.reload_plugins
        )

        self.delete_btn.clicked.connect(
            self.delete_plugin
        )

        self.reload()

    def selected_plugin(
        self,
    ) -> str | None:
        index = (
            self.plugins_table.currentIndex()
        )

        if not index.isValid():
            return None

        row = self.model.row(
            index.row()
        )

        return row.get(
            "name"
        )

    def install_file(
        self,
    ) -> None:
        path, _ = (
            QFileDialog.getOpenFileName(
                self,
                "Install Plugin",
                "",
                "*.plugin.js",
            )
        )

        if not path:
            return

        try:
            self.browser.install_plugin_file(
                path
            )

            self.reload()

        except Exception as exc:
            QMessageBox.warning(
                self,
                "Install Failed",
                str(exc),
            )

    # v2
    def install_url(
        self,
    ) -> None:
        url, accepted = (
            QInputDialog.getText(
                self,
                "Install URL",
                "Plugin URL",
            )
        )

        url = url.strip()

        if (
            not accepted
            or not url
        ):
            return

        try:
            self.browser.install_plugin_url(
                url
            )

            self.reload()

        except Exception as exc:
            QMessageBox.warning(
                self,
                "Install Failed",
                str(exc),
            )

    def enable_plugin(
        self,
    ) -> None:
        plugin = (
            self.selected_plugin()
        )

        if not plugin:
            return

        self.browser.set_plugin_enabled(
            plugin,
            True,
        )

        self.reload()

    def disable_plugin(
        self,
    ) -> None:
        plugin = (
            self.selected_plugin()
        )

        if not plugin:
            return

        self.browser.set_plugin_enabled(
            plugin,
            False,
        )

        self.reload()

    def delete_plugin(
        self,
    ) -> None:
        plugin = (
            self.selected_plugin()
        )

        if not plugin:
            return

        result = (
            QMessageBox.question(
                self,
                "Delete Plugin",
                plugin,
            )
        )

        if (
            result
            != QMessageBox.StandardButton.Yes
        ):
            return

        self.browser.remove_plugin(
            plugin
        )

        self.reload()

    def reload_plugins(
        self,
    ) -> None:
        self.browser.reload_addons()
        self.reload()

    def reload(
        self,
    ) -> None:
        self.model.set_plugins(
            self.browser.plugin_inventory()
        )

        self.logs_model.set_rows(
            list(
                self.db.plugin_logs(
                    self.profile_name
                )
            )
        )

        self.crash_model.set_rows(
            list(
                self.db.plugin_crashes(
                    self.profile_name
                )
            )
        )

        self.diagnostics.setPlainText(
            json.dumps(
                self.browser.plugin_diagnostics_snapshot(),
                indent=2,
                ensure_ascii=False,
            )
        )
class SettingsWindow(QDialog):
    settingsChanged = Signal()

    def __init__(self, settings: dict, parent=None):
        super().__init__(parent)
        self.settings = settings
        self.setWindowTitle("Settings")
        self.resize(950, 700)

        tabs = QTabWidget()
        tabs.addTab(self._general_tab(), "General")
        tabs.addTab(self._network_tab(), "Network")
        tabs.addTab(self._permissions_tab(), "Permissions")
        tabs.addTab(self._privacy_tab(), "Privacy")
        tabs.addTab(self._developer_tab(), "Developer")

        layout = QVBoxLayout(self)
        layout.addWidget(tabs)

        save_button = QPushButton("Save")
        save_button.clicked.connect(self._save)
        layout.addWidget(save_button)

    def _general_tab(self):
        widget = QWidget()
        layout = QFormLayout(widget)

        self.download_path = QLineEdit(
            self.settings.get("download_directory", "")
        )

        layout.addRow("Downloads", self.download_path)
        return widget

    def _network_tab(self):
        widget = QWidget()
        layout = QFormLayout(widget)

        self.network_logging = QCheckBox()
        self.network_logging.setChecked(
            self.settings.get("network_logging", True)
        )

        layout.addRow("Enable Logging", self.network_logging)
        return widget

    def _permissions_tab(self):
        widget = QWidget()
        layout = QVBoxLayout(widget)

        permissions = self.settings.setdefault(
            "permissions",
            {},
        )

        self.permission_boxes = {}

        for key, title in (
            ("microphone", "Microphone"),
            ("camera", "Camera"),
            ("clipboard", "Clipboard"),
            ("notifications", "Notifications"),
            ("downloads", "Downloads"),
            ("fullscreen", "Fullscreen"),
        ):
            box = QCheckBox(title)
            box.setChecked(
                bool(
                    permissions.get(
                        key,
                        False,
                    )
                )
            )
            self.permission_boxes[key] = box
            layout.addWidget(box)

        layout.addStretch()
        return widget

    def _privacy_tab(self):
        widget = QWidget()
        layout = QVBoxLayout(widget)

        blocked = set(
            self.settings.get(
                "blocked_hosts",
                [],
            )
        )

        self.discord_telemetry = QCheckBox(
            "Block Discord Telemetry"
        )
        self.discord_telemetry.setChecked(
            "telemetry.discord.com" in blocked
        )

        self.discord_crash = QCheckBox(
            "Block Discord Crash Reporting"
        )
        self.discord_crash.setChecked(
            "crash.discord.com" in blocked
        )

        self.sentry = QCheckBox(
            "Block Sentry"
        )
        self.sentry.setChecked(
            "sentry.io" in blocked
        )

        self.statsig = QCheckBox(
            "Block Statsig"
        )
        self.statsig.setChecked(
            "api.statsig.com" in blocked
        )

        layout.addWidget(self.discord_telemetry)
        layout.addWidget(self.discord_crash)
        layout.addWidget(self.sentry)
        layout.addWidget(self.statsig)

        group = QGroupBox(
            "Blocked Hosts"
        )

        group_layout = QVBoxLayout(group)

        self.blocked_hosts = QPlainTextEdit()
        self.blocked_hosts.setPlainText(
            "\n".join(
                sorted(blocked)
            )
        )

        group_layout.addWidget(
            self.blocked_hosts
        )

        layout.addWidget(group)

        return widget

    def _developer_tab(self):
        widget = QWidget()
        layout = QFormLayout(widget)

        self.enable_cdp = QCheckBox()

        self.enable_cdp.setChecked(
            self.settings.get(
                "enable_cdp",
                True,
            )
        )

        layout.addRow(
            "Enable CDP",
            self.enable_cdp,
        )

        return widget

    def _save(self):
        self.settings["download_directory"] = (
            self.download_path.text().strip()
        )

        self.settings["network_logging"] = (
            self.network_logging.isChecked()
        )

        self.settings["enable_cdp"] = (
            self.enable_cdp.isChecked()
        )

        permissions = self.settings.setdefault(
            "permissions",
            {},
        )

        for key, box in self.permission_boxes.items():
            permissions[key] = (
                box.isChecked()
            )

        blocked = {
            line.strip().lower()
            for line in self.blocked_hosts.toPlainText().splitlines()
            if line.strip()
        }

        if self.discord_telemetry.isChecked():
            blocked.add(
                "telemetry.discord.com"
            )

        if self.discord_crash.isChecked():
            blocked.add(
                "crash.discord.com"
            )

        if self.sentry.isChecked():
            blocked.add("sentry.io")

        if self.statsig.isChecked():
            blocked.add(
                "api.statsig.com"
            )

        self.settings[
            "blocked_hosts"
        ] = sorted(blocked)

        self.settingsChanged.emit()
        self.accept()

# v4
class ProfileManager(QDialog):
    profileSelected = Signal(str)

    PROFILE_MAX_FILES = 10000
    PROFILE_MAX_FILE_SIZE = 50 * 1024 * 1024
    PROFILE_MAX_TOTAL_SIZE = 500 * 1024 * 1024

    PROFILE_EXPORT_SKIP = {
        "cache",
        "GPUCache",
        "Code Cache",
        "blob_storage",
        "Service Worker",
        "Session Storage",
    }

    def __init__(
        self,
        profiles: list[str],
        current: str,
        parent=None,
    ):
        super().__init__(parent)

        self.setWindowTitle(
            "Profiles"
        )

        self.resize(500, 550)

        self.list_widget = QListWidget()

        profiles = sorted(profiles)

        self.list_widget.addItems(
            profiles
        )

        if current in profiles:
            self.list_widget.setCurrentRow(
                profiles.index(current)
            )

        switch_button = QPushButton(
            "Switch"
        )

        export_button = QPushButton(
            "Export"
        )

        import_button = QPushButton(
            "Import"
        )

        switch_button.clicked.connect(
            self._switch
        )

        export_button.clicked.connect(
            self._export
        )

        import_button.clicked.connect(
            self._import
        )

        buttons = QHBoxLayout()
        buttons.addWidget(switch_button)
        buttons.addWidget(export_button)
        buttons.addWidget(import_button)

        layout = QVBoxLayout(self)
        layout.addWidget(self.list_widget)
        layout.addLayout(buttons)

    def _switch(
        self,
    ):
        item = self.list_widget.currentItem()

        if not item:
            return

        self.profileSelected.emit(
            item.text()
        )

        self.accept()

    def _export(
        self,
    ):
        item = (
            self.list_widget.currentItem()
        )

        if not item:
            return

        profile_name = (
            item.text()
        )

        path, _ = (
            QFileDialog.getSaveFileName(
                self,
                "Export Profile",
                f"{profile_name}.profilebundle",
                "*.profilebundle",
            )
        )

        if not path:
            return

        profile_root_path = (
            profile_root(
                profile_name
            )
        ).resolve()

        export = {
            "version": 1,
            "profile": profile_name,
            "storage": {},
        }

        file_count = 0
        total_size = 0

        for file in (
            profile_root_path.rglob(
                "*"
            )
        ):
            if (
                not file.is_file()
                or file.is_symlink()
            ):
                continue

            try:
                resolved = (
                    file.resolve()
                )

                if (
                    profile_root_path
                    not in resolved.parents
                ):
                    continue

                relative = (
                    resolved.relative_to(
                        profile_root_path
                    )
                )

                if any(
                    part
                    in self.PROFILE_EXPORT_SKIP
                    for part in relative.parts
                ):
                    continue

                size = (
                    resolved.stat()
                    .st_size
                )

                if (
                    size
                    > self.PROFILE_MAX_FILE_SIZE
                ):
                    continue

                file_count += 1
                total_size += size

                if (
                    file_count
                    > self.PROFILE_MAX_FILES
                ):
                    raise ValueError(
                        "profile exceeds file limit"
                    )

                if (
                    total_size
                    > self.PROFILE_MAX_TOTAL_SIZE
                ):
                    raise ValueError(
                        "profile exceeds size limit"
                    )

                export[
                    "storage"
                ][
                    str(relative)
                ] = (
                    resolved.read_bytes()
                    .hex()
                )

            except Exception:
                raise

        Path(path).write_text(
            json.dumps(
                export,
                indent=2,
                ensure_ascii=False,
            ),
            encoding="utf-8",
        )

        QMessageBox.information(
            self,
            "Export",
            "Profile exported.",
        )

    def _import(
        self,
    ):
        path, _ = (
            QFileDialog.getOpenFileName(
                self,
                "Import Profile",
                "",
                "*.profilebundle",
            )
        )

        if not path:
            return

        try:
            bundle = json.loads(
                Path(path).read_text(
                    encoding="utf-8"
                )
            )

            profile_name = str(
                bundle.get(
                    "profile",
                    "",
                )
            ).strip()

            if not profile_name:
                raise ValueError(
                    "invalid profile"
                )

            root = profile_root(
                profile_name
            )

            root.mkdir(
                parents=True,
                exist_ok=True,
            )

            root_resolved = (
                root.resolve()
            )

            file_count = 0
            total_size = 0

            for (
                relative,
                payload,
            ) in bundle.get(
                "storage",
                {},
            ).items():

                file_count += 1

                if (
                    file_count
                    > self.PROFILE_MAX_FILES
                ):
                    raise ValueError(
                        "profile exceeds file limit"
                    )

                size = (
                    len(payload)
                    // 2
                )

                if (
                    size
                    > self.PROFILE_MAX_FILE_SIZE
                ):
                    continue

                total_size += size

                if (
                    total_size
                    > self.PROFILE_MAX_TOTAL_SIZE
                ):
                    raise ValueError(
                        "profile exceeds size limit"
                    )

                target = (
                    root
                    / relative
                ).resolve()

                if (
                    root_resolved
                    not in target.parents
                    and target
                    != root_resolved
                ):
                    continue

                target.parent.mkdir(
                    parents=True,
                    exist_ok=True,
                )

                target.write_bytes(
                    bytes.fromhex(
                        payload
                    )
                )

            QMessageBox.information(
                self,
                "Import",
                "Profile imported.",
            )

        except Exception as exc:
            QMessageBox.warning(
                self,
                "Import",
                str(exc),
            )
class AddonManagerWindow(QDialog):
    def __init__(
        self,
        context: ProfileContext,
        parent=None,
    ):
        super().__init__(parent)

        self.context = context

        self.manager = AddonManager(
            context.name,
            getattr(
                parent,
                "db",
                None,
            ),
        )

        self.setWindowTitle(
            "Themes & Extensions"
        )

        self.resize(900, 650)

        self.model = AddonModel()

        self.table = QTableView()
        self.table.setModel(self.model)

        refresh = QPushButton(
            "Refresh"
        )

        refresh.clicked.connect(
            self.reload
        )

        layout = QVBoxLayout(self)
        layout.addWidget(self.table)
        layout.addWidget(refresh)

        self.reload()


    def reload(
        self,
    ):
        rows = []

        for theme in (
            self.manager.themes.discover()
        ):
            rows.append(
                {
                    "name": theme.stem,
                    "enabled": True,
                    "type": "Theme",
                }
            )

        for extension in (
            self.manager.extensions.discover()
        ):
            enabled = True

            manifest = (
                extension
                / "manifest.json"
            )

            if manifest.exists():
                try:
                    enabled = bool(
                        json.loads(
                            manifest.read_text(
                                encoding="utf-8"
                            )
                        ).get(
                            "enabled",
                            True,
                        )
                    )
                except Exception:
                    pass

            rows.append(
                {
                    "name": extension.name,
                    "enabled": enabled,
                    "type": "Extension",
                }
            )

        for plugin in (
            self.manager.plugins.inventory()
        ):
            rows.append(
                {
                    "name": plugin[
                        "name"
                    ],
                    "enabled": plugin.get(
                        "enabled",
                        True,
                    ),
                    "type": "Plugin",
                }
            )

        self.model.set_addons(
            rows
        )


class ThemeEditor(QDialog):
    def __init__(
        self,
        theme_path: Path,
        parent=None,
    ):
        super().__init__(parent)

        self.theme_path = theme_path

        self.setWindowTitle(
            theme_path.name
        )

        self.resize(1000, 700)

        self.editor = QPlainTextEdit()

        if theme_path.exists():
            self.editor.setPlainText(
                theme_path.read_text(
                    encoding="utf-8"
                )
            )

        save_button = QPushButton(
            "Save"
        )

        save_button.clicked.connect(
            self.save
        )

        layout = QVBoxLayout(self)
        layout.addWidget(self.editor)
        layout.addWidget(save_button)

    def save(self):
        self.theme_path.write_text(
            self.editor.toPlainText(),
            encoding="utf-8",
        )
        self.accept()


class ProxyManagerWindow(QDialog):
    def __init__(
        self,
        network,
        profile_name: str,
        parent=None,
    ):
        super().__init__(parent)

        self.network = network
        self.profile_name = profile_name

        self.setWindowTitle(
            "Proxy Settings"
        )

        self.resize(500, 350)

        proxy = (
            network.proxy.get_proxy(
                profile_name
            )
        )

        self.enabled = QCheckBox()
        self.enabled.setChecked(
            proxy.enabled
        )

        self.host = QLineEdit(
            proxy.host
        )

        self.port = QLineEdit(
            str(proxy.port)
        )

        self.username = QLineEdit(
            proxy.username
        )

        self.password = QLineEdit(
            proxy.password
        )

        self.type_box = QLineEdit(
            proxy.proxy_type
        )

        layout = QFormLayout(self)

        layout.addRow(
            "Enabled",
            self.enabled,
        )

        layout.addRow(
            "Type",
            self.type_box,
        )

        layout.addRow(
            "Host",
            self.host,
        )

        layout.addRow(
            "Port",
            self.port,
        )

        layout.addRow(
            "Username",
            self.username,
        )

        layout.addRow(
            "Password",
            self.password,
        )

        save_button = QPushButton(
            "Save"
        )

        save_button.clicked.connect(
            self.save
        )

        layout.addRow(save_button)

    def save(self):
        try:
            port = int(
                self.port.text().strip()
                or "0"
            )
        except ValueError:
            port = 0

        proxy = ProxyConfig(
            enabled=self.enabled.isChecked(),
            proxy_type=self.type_box.text().strip() or "http",
            host=self.host.text().strip(),
            port=port,
            username=self.username.text(),
            password=self.password.text(),
        )

        self.network.proxy.save_proxy(
            self.profile_name,
            proxy,
        )

        self.accept()


class NetworkInspector(QMainWindow):
    def __init__(
        self,
        network,
        parent=None,
    ):
        super().__init__(parent)

        self.network = network

        self.setWindowTitle(
            "Network Inspector"
        )

        self.resize(1400, 850)

        self.model = NetworkTableModel()

        self.table = QTableView()
        self.table.setModel(self.model)

        self.table.horizontalHeader().setStretchLastSection(
            True
        )

        self.search = QLineEdit()
        self.search.setPlaceholderText(
            "Search URL, host, method..."
        )

        self.search.textChanged.connect(
            self.reload
        )

        self.details = QPlainTextEdit()
        self.details.setReadOnly(True)

        splitter = QSplitter(
            Qt.Orientation.Vertical
        )

        splitter.addWidget(
            self.table
        )

        splitter.addWidget(
            self.details
        )

        container = QWidget()

        layout = QVBoxLayout(
            container
        )

        layout.addWidget(
            self.search
        )

        layout.addWidget(
            splitter
        )

        self.setCentralWidget(
            container
        )

        self.table.clicked.connect(
            self.show_details
        )

        self.reload()

    def reload(self):
        query = (
            self.search.text().strip()
        )

        rows = (
            self.network.search(query)
            if query
            else self.network.recent()
        )

        self.model.set_rows(rows)

    def show_details(
        self,
        index,
    ):
        try:
            row = self.model.row(
                index.row()
            )

            self.details.setPlainText(
                json.dumps(
                    dict(row),
                    indent=2,
                    ensure_ascii=False,
                )
            )

        except Exception:
            self.details.clear()


# v2
class TrayManager(QObject):
    def __init__(
        self,
        parent,
    ):
        super().__init__(parent)

        self.parent_window = parent

        self.tray = QSystemTrayIcon(
            parent
        )

        icon = (
            QApplication.windowIcon()
        )

        if icon.isNull():
            icon = (
                self.parent_window.windowIcon()
            )

        self.tray.setIcon(
            icon
        )

        self.menu = QMenu()

        self.open_action = QAction(
            "Open",
            parent,
        )

        self.hide_action = QAction(
            "Hide",
            parent,
        )

        self.quit_action = QAction(
            "Quit",
            parent,
        )

        self.menu.addAction(
            self.open_action
        )

        self.menu.addAction(
            self.hide_action
        )

        self.menu.addSeparator()

        self.menu.addAction(
            self.quit_action
        )

        self.tray.setContextMenu(
            self.menu
        )

        self.open_action.triggered.connect(
            self.show_window
        )

        self.hide_action.triggered.connect(
            parent.hide
        )

        self.quit_action.triggered.connect(
            self.quit_application
        )

        self.tray.activated.connect(
            self._activated
        )

    def _activated(
        self,
        reason,
    ):
        if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
            self.show_window()

    def show_window(self):
        self.parent_window.showNormal()
        self.parent_window.raise_()
        self.parent_window.activateWindow()

    def quit_application(self):
        self.tray.hide()
        self.parent_window.tray_manager = None
        self.parent_window.settings_data.setdefault(
            "tray",
            {}
        )["close_to_tray"] = False
        self.parent_window.close()