"""A small busy-feedback helper for synchronous (UI-thread) engine calls.

The Scenario Builder model editors and the model-version compare shell out to the
.NET engine synchronously, which blocks the UI thread for the duration. Without
feedback the window looks hung. ``busy_progress`` is a context manager that, for
the wrapped operation:
  - disables the triggering button(s),
  - shows a wait cursor,
  - shows a modal indeterminate progress dialog with a "Starting…"-style message.

The progress dialog can't animate during a truly blocking call, but it + the
greyed buttons + wait cursor make it unambiguous that work is in progress, and it
is always torn down (cursor restored, buttons re-enabled) on exit, even on error.
"""

from __future__ import annotations

from contextlib import contextmanager

from ..qt_compat import QApplication, QProgressDialog, Qt
from ..translator import tr


@contextmanager
def busy_progress(parent, message: str, *, disable=()):
    """Show busy feedback around a synchronous operation.

    Args:
        parent: parent widget for the modal progress dialog.
        message: already-localized label, e.g. tr("provenance.busy_applying_weights").
        disable: iterable of widgets (buttons) to disable for the duration.
    """
    widgets = [w for w in (disable or []) if w is not None]
    for w in widgets:
        try:
            w.setEnabled(False)
        except Exception:
            pass

    dialog = None
    try:
        # range 0..0 => indeterminate "busy" indicator; no cancel button.
        dialog = QProgressDialog(message, "", 0, 0, parent)
        dialog.setWindowTitle(tr("provenance.busy_title"))
        dialog.setCancelButton(None)
        dialog.setMinimumDuration(0)
        dialog.setAutoClose(False)
        dialog.setAutoReset(False)
        dialog.setWindowModality(Qt.WindowModality.ApplicationModal)
    except Exception:
        dialog = None

    try:
        QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor)
    except Exception:
        pass

    try:
        if dialog is not None:
            dialog.show()
        QApplication.processEvents()  # paint the dialog before the blocking call
        yield dialog
    finally:
        try:
            # Drain any override cursors still on the stack — our own WaitCursor
            # plus any that the wrapped call (e.g. a QGIS processing algorithm)
            # pushed and failed to pop — so the cursor never stays stuck as an
            # hourglass after the operation. Capped to avoid a pathological loop.
            for _ in range(16):
                if QApplication.overrideCursor() is None:
                    break
                QApplication.restoreOverrideCursor()
        except Exception:
            pass
        if dialog is not None:
            try:
                dialog.close()
            except Exception:
                pass
        for w in widgets:
            try:
                w.setEnabled(True)
            except Exception:
                pass
        try:
            QApplication.processEvents()
        except Exception:
            pass
