# SPDX-FileCopyrightText: 2025 XLeitstelle Planen und Bauen <xleitstelle@gv.hamburg.de>
# SPDX-FileContributor: Tobias Kraft <tobias.kraft@gv.hamburg.de>
#
# SPDX-License-Identifier: EUPL-1.2

"""PlanTools tab — web view for plan-level operations.

`PlanTools` manages the *PlanTools* tab inside the dock widget.  It hosts a
`QWebEngineView` that loads a dedicated page of the XMAS-App and exposes a
`PlanToolsHandler` over `QWebChannel` so the JavaScript frontend can trigger
QGIS operations (e.g. switching the active plan).

The combo box above the web view lets the user select the current plan; the
view is loaded lazily when the tab is first opened and cleared when the tab
is closed to avoid unnecessary background network activity.
"""

import logging
from typing import TYPE_CHECKING

from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineView
from qgis.PyQt import sip
from qgis.PyQt.QtCore import QObject, Qt, QUrl
from qgis.PyQt.QtWidgets import QComboBox

from xmas_plugin.settings_manager import get_normalized_url
from xmas_plugin.util.metadata import PLUGIN_DIR_NAME
from xmas_plugin.util.plan_combobox import PlanComboboxManager
from xmas_plugin.util.webengine import detach_channel, wire_page_lifecycle
from xmas_plugin.widgets.handler import PlanToolsHandler
from xmas_plugin.widgets.page import XMASPluginPage

if TYPE_CHECKING:
    from xmas_plugin.dock import XMASPluginDockWidget  # Only for IDE/type checker

logger = logging.getLogger(PLUGIN_DIR_NAME)


class PlanTools(QObject):
    """Encapsulates the PlanTools page and wires up signals for QGIS interaction."""

    def __init__(self, parent: "XMASPluginDockWidget") -> None:
        super().__init__(parent)
        self.parent = parent
        self.iface = parent.iface

        self.web_view: QWebEngineView = self.parent.findChild(
            QWebEngineView, "planToolsView"
        )
        self._base_url = get_normalized_url()
        self._setup_plan_tools()

        # PlanComboboxManager
        self.comboBox_plans: QComboBox = self.parent.findChild(
            QComboBox, "planToolsComboBox"
        )
        self.plan_combobox_manager = PlanComboboxManager(
            self.comboBox_plans, self._clear_plan_tools
        )
        # use generic QgsProject signals to track addition/removal of plugin layers
        self.comboBox_plans.currentIndexChanged.connect(self.on_current_plan_changed)

        # load/clear web_view depending on tab visibility
        self.parent.tabWidget.currentChanged.connect(self.on_tab_open)
        self.parent.tabWidget.tabCloseRequested.connect(self.on_tab_close)
        self._clear_plan_tools()

    def on_current_plan_changed(self, index: int) -> None:
        """Load the plan tree when selection has changed and tab is visible."""
        plan_data = self.comboBox_plans.itemData(index)
        # only load view if it's visibible, otherwise defer to on_tab_open
        if plan_data and self.web_view.isVisible():
            self._load_plan_tools(plan_data["plan_id"])

    def on_tab_open(self, index: int) -> None:
        """Load the web view when planTools tab is opened."""
        if (
            index == self.parent.tabWidget.indexOf(self.parent.planTools)
            and self.web_view.url().toString() == "about:blank"
        ):
            self.on_current_plan_changed(self.comboBox_plans.currentIndex())

    def on_tab_close(self, index: int) -> None:
        """Reset the web view when the planTools tab is closed."""
        if self.parent.tabWidget.indexOf(self.parent.planTools) == index:
            self._clear_plan_tools()

    def _setup_plan_tools(self) -> None:
        """Create/prepare the plan tools QWebEngineView + WebChannel (idempotent)."""
        detach_channel(self.web_view)  # unplug stale channel if any

        # Settings
        self.web_view.settings().setAttribute(
            QWebEngineSettings.ErrorPageEnabled, False
        )

        # Pause health polling during heavier loads (connect once)
        try:
            self.web_view.loadStarted.connect(
                lambda: self.parent._pause_polls(True),
                type=Qt.UniqueConnection,
            )
        except TypeError:
            pass
        try:
            self.web_view.loadFinished.connect(
                lambda _: self.parent._pause_polls(False),
                type=Qt.UniqueConnection,
            )
        except TypeError:
            pass

        # Cleanly unplug channel if the view is destroyed
        try:
            self.web_view.destroyed.connect(
                lambda: detach_channel(self.web_view), type=Qt.UniqueConnection
            )
        except TypeError:
            pass

        # Page
        self.web_view.setPage(XMASPluginPage(self))
        # wire webchannel and handler
        wire_page_lifecycle(self.web_view, self._make_assoc_handler)

    def _clear_plan_tools(self):
        """Reset web view to blank url."""
        self.web_view.load(QUrl("about:blank"))

    def _load_plan_tools(self, plan_id: str | None) -> None:
        """Guarded navigation into the plan tools view."""
        from xmas_plugin.dock import ServerState

        if not plan_id:
            return self._clear_plan_tools()
        if self.parent.server_state != ServerState.RUNNING:
            logger.info("PlanTools: skip load (server not RUNNING)")
            return self._clear_plan_tools()

        if not self.web_view or sip.isdeleted(self.web_view):
            logger.warning("PlanTools: view missing/deleted; not loading")
            return

        if not self._base_url:
            self._base_url = get_normalized_url()

        url = QUrl(f"{self._base_url}/plan-tools/{plan_id}")

        logger.info("PlanTools: loading %s", url.toString())
        self.web_view.load(url)

    def _make_assoc_handler(self, parent_channel):
        """Create JS<->Python call handler for the plan tools view."""
        handler = PlanToolsHandler(parent_channel)
        return handler
