"""Modal action-required prompts for the desktop agent console."""

from __future__ import annotations

import threading
import time
from typing import Any, Callable


ModalButton = tuple[str, str]


def show_modal_action_required(
    gtk_module: Any,
    adw_module: Any,
    *,
    parent: Any,
    title: str,
    body: str,
    buttons: list[ModalButton],
    on_response: Callable[[str], None],
    default_button: str | None = None,
) -> str:
    """
    Present a modal, always-on-top action dialog transient to the main window.

    Returns ``async`` when shown, ``unavailable`` when no dialog backend exists.
    """

    if gtk_module is not None and callable(getattr(gtk_module, "AlertDialog", None)):
        return _show_alert_dialog(
            gtk_module,
            parent=parent,
            title=title,
            body=body,
            buttons=buttons,
            on_response=on_response,
            default_button=default_button,
        )

    if adw_module is not None and callable(getattr(adw_module, "MessageDialog", None)):
        return _show_message_dialog(
            adw_module,
            parent=parent,
            title=title,
            body=body,
            buttons=buttons,
            on_response=on_response,
            default_button=default_button,
        )

    return "unavailable"


def show_modal_action_required_blocking(
    gtk_module: Any,
    adw_module: Any,
    glib_module: Any,
    *,
    parent: Any,
    title: str,
    body: str,
    buttons: list[ModalButton],
    default_button: str | None = None,
    timeout_seconds: float | None = None,
    cancel_event: threading.Event | None = None,
) -> str:
    """Block the calling thread until the user picks a button (for plan worker threads)."""

    result: dict[str, str | None] = {"response": None}
    event = threading.Event()
    fallback_id = default_button or (buttons[-1][0] if buttons else "ok")

    def _finish(response_id: str) -> None:
        result["response"] = response_id
        event.set()

    def _present() -> bool:
        outcome = show_modal_action_required(
            gtk_module,
            adw_module,
            parent=parent,
            title=title,
            body=body,
            buttons=buttons,
            on_response=_finish,
            default_button=default_button,
        )
        if outcome == "unavailable":
            _finish(fallback_id)
        return False

    glib_module.idle_add(_present)
    deadline = None
    if timeout_seconds is not None and timeout_seconds > 0:
        deadline = time.monotonic() + float(timeout_seconds)
    poll_sec = 0.2
    while not event.wait(timeout=poll_sec):
        if cancel_event is not None and cancel_event.is_set():
            return "cancel"
        if deadline is not None and time.monotonic() >= deadline:
            return fallback_id
    return str(result["response"] or fallback_id)


def _show_alert_dialog(
    gtk_module: Any,
    *,
    parent: Any,
    title: str,
    body: str,
    buttons: list[ModalButton],
    on_response: Callable[[str], None],
    default_button: str | None,
) -> str:
    alert_cls = gtk_module.AlertDialog
    dialog = alert_cls()
    dialog.set_message(title)
    if body:
        dialog.set_detail(body)
    labels = [label for _button_id, label in buttons]
    dialog.set_buttons(labels)
    dialog.set_cancel_button(0)
    default_index = 0
    if default_button:
        for index, (button_id, _label) in enumerate(buttons):
            if button_id == default_button:
                default_index = index
                break
    else:
        default_index = max(0, len(buttons) - 1)
    dialog.set_default_button(default_index)
    dialog.set_modal(True)

    def _finish(_source: Any, result: Any, *_args: Any) -> None:
        try:
            index = int(dialog.choose_finish(result))
        except Exception:
            on_response(buttons[0][0])
            return
        if index < 0 or index >= len(buttons):
            on_response(buttons[0][0])
            return
        on_response(buttons[index][0])

    try:
        dialog.choose(parent, None, _finish, None)
    except Exception:
        return "unavailable"
    return "async"


def _show_message_dialog(
    adw_module: Any,
    *,
    parent: Any,
    title: str,
    body: str,
    buttons: list[ModalButton],
    on_response: Callable[[str], None],
    default_button: str | None,
) -> str:
    dialog = adw_module.MessageDialog(transient_for=parent, heading=title, body=body)
    dialog.set_modal(True)
    for button_id, label in buttons:
        dialog.add_response(button_id, label)
        if button_id == (default_button or buttons[-1][0]):
            dialog.set_default_response(button_id)
            dialog.set_response_appearance(button_id, adw_module.ResponseAppearance.SUGGESTED)
    close_id = default_button or buttons[-1][0]
    dialog.set_close_response(close_id)

    def _on_response(_dialog, response: str) -> None:
        on_response(str(response))

    dialog.connect("response", _on_response)
    try:
        if parent is not None and hasattr(parent, "present"):
            parent.present()
        dialog.present()
    except Exception:
        return "unavailable"
    return "async"
