# SPDX-FileCopyrightText: 2025 XLeitstelle Planen und Bauen <xleitstelle@gv.hamburg.de>
# SPDX-FileContributor: Anton Jacobsson <anton.jacobsson@init.de>
#
# SPDX-License-Identifier: EUPL-1.2-or-later

import logging

from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtWebEngineWidgets import QWebEngineView
from qgis.PyQt import sip

from xmas_plugin.util.metadata import PLUGIN_DIR_NAME

logger = logging.getLogger(PLUGIN_DIR_NAME)


# Verify if Qt-Objekt exists or not
def _is_valid_qt(obj):
    try:
        return obj is not None and not sip.isdeleted(obj)
    except Exception:
        return False


def _hasattr_safe(obj, name):
    try:
        getattr(obj, name)
        return True
    except Exception:
        return False


def detach_channel(view):
    # Only handle real QWebEngineView instances that are still valid
    if not _is_valid_qt(view) or not isinstance(view, QWebEngineView):
        logging.debug("detach_channel: skip (not valid or not a QWebEngineView)")
        return

    try:
        page = view.page() if _hasattr_safe(view, "page") else None
    except Exception:
        page = None

    logging.debug(
        "detach_channel: view=%r page=%r channel(before)=%r",
        id(view),
        id(page) if page else None,
        getattr(view, "channel", None),
    )

    chan = getattr(view, "channel", None)
    h = getattr(view, "handler", None)

    if _is_valid_qt(page):
        try:
            page.setWebChannel(None)
        except Exception:
            pass

    # Deregister if both are still valid Qt objects
    if _is_valid_qt(chan) and _is_valid_qt(h):
        try:
            chan.deregisterObject(h)
        except Exception:
            pass

    logging.debug("detach_channel: DETACHED channel=None handler=None")
    # Clear attributes defensively
    try:
        view.handler = None
        view.channel = None
    except Exception:
        pass

    logging.debug("detach_channel: DETACHED channel=None handler=None")


def attach_channel(view, handler_factory):
    if not _is_valid_qt(view):
        return
    page = view.page()
    if not _is_valid_qt(page):
        return
    logging.debug(
        "attach_channel: view=%r page=%r existing=%r",
        id(view),
        id(page),
        getattr(view, "channel", None),
    )

    existing = getattr(view, "channel", None)
    if _is_valid_qt(existing) and existing.parent() is page:
        return

    if _is_valid_qt(existing):
        detach_channel(view)

    channel = QWebChannel(page)  # parent = page
    handler = handler_factory(channel)  # parent = chan (enforce in factory)
    channel.registerObject("handler", handler)
    page.setWebChannel(channel)
    view.channel = channel
    view.handler = handler
    logging.debug(
        "attach_channel: ATTACHED channel=%r handler=%r parent(page)=%r",
        id(channel),
        id(handler),
        id(page),
    )


def wire_page_lifecycle(view, handler_factory):
    # Attach once immediately so the very first navigation has a channel
    attach_channel(view, handler_factory)
    # Keep it attached across loads; do NOT detach on loadStarted.

    def _ensure_attached_after_load(ok):
        if ok:
            attach_channel(view, handler_factory)  # idempotent for same page

        else:
            # on failed load, still keep the current channel; no-op
            pass

    view.loadFinished.connect(_ensure_attached_after_load)

    # If the render process dies, detach (page still exists but backend is gone)
    try:
        page = view.page()
        if page:
            page.renderProcessTerminated.connect(lambda *_: detach_channel(view))
    except AttributeError:
        pass

    # If Qt >= 5.14: re-attach when the VIEW gets a brand-new PAGE object
    try:
        view.pageChanged.connect(lambda _p: attach_channel(view, handler_factory))
    except Exception:
        pass


def cleanup_webengine_view(view):
    logger.info("Cleanup Webengine View called.")
    # unplug safely (idempotent)
    detach_channel(view)

    # deleting the view widget, only if qt object is valid
    if _is_valid_qt(view) and isinstance(view, QWebEngineView):
        try:
            # Break lifetime chain explicitly (guard page existence)
            if _is_valid_qt(view.page()):
                view.setPage(None)
        except Exception:
            pass

        try:
            setattr(view, "_pm_detached", True)
        except Exception:
            pass


def cleanup_all_webengine_views(parent_widget):
    views = parent_widget.findChildren(QWebEngineView)
    for v in views:
        cleanup_webengine_view(v)


def destroy_all_webengine_views(parent_widget):
    """Final teardown (plugin unload): also delete the widgets."""
    views = parent_widget.findChildren(QWebEngineView)
    logger.info(
        f"Found {len(views)} QWebEngineView(s) for destruction in {parent_widget}"
    )
    for view in views:
        try:
            cleanup_webengine_view(view)
        finally:
            try:
                view.deleteLater()
            except Exception:
                pass


def on_view_destroyed(obj=None):
    detach_channel(obj)
