"""Confirmation interface module."""

from asyncio import Future, get_running_loop
from typing import Literal

import discord
from typing_extensions import Self, override

from discordcn.pycord._utils import maybe_awaitable, safe_set_future_exception, safe_set_future_result

from ._types import Callback


class ConfirmInterface(discord.ui.DesignerView):
    """Interactive confirmation UI with confirm/cancel buttons.

    This view presents a message with confirm and cancel buttons and exposes the
    user's decision via an awaitable Future. Multiple coroutines may await the
    result using `wait()`.

    Result semantics:
        - True: The confirm button was pressed.
        - False: The cancel button was pressed.
        - TimeoutError: The view timed out before a decision was made.

    The interaction can optionally be restricted to a single user, and custom
    callbacks may be provided for confirm and cancel actions.

    Notes:
        - The underlying Future is completed exactly once.
        - On timeout, the Future is completed with a TimeoutError.
        - The user is responsible for handling the
          interaction response.

    Attributes:
        header (str): The title text displayed at the top of the confirmation
            message.
        description (str): The main body text describing the action to be
            confirmed.
        footer (str | None): Optional footer text displayed below the main
            content.
        variant (Literal["default", "danger"]): Visual style of the interface.
            The ``"danger"`` variant highlights destructive actions.
        user_id (int | None): If set, only interactions from this user are
            accepted.
        yes_label (str): Label used for the confirm button.
        no_label (str): Label used for the cancel button.
        emojis (tuple[str | None, str | None]): Emojis used for the cancel and
            confirm buttons, respectively. Values may be ``None`` if emojis are
            disabled.
        cancel_callback (Callback[discord.Interaction] | None): Callback
            function to handle cancel button interactions.
        confirm_callback (Callback[discord.Interaction] | None): Callback
            function to handle confirm button interactions.
    """

    @override
    def __init__(
        self,
        header: str,
        description: str,
        *,
        footer: str | None = None,
        variant: Literal["default", "danger"] = "default",
        emojis: bool | tuple[str, str] = False,
        timeout: int = 900,
        user_id: int | None = None,
        yes_label: str = "Confirm",
        no_label: str = "Cancel",
        cancel_callback: Callback[discord.Interaction] | None = None,
        confirm_callback: Callback[discord.Interaction] | None = None,
    ) -> None:
        """Initialize a confirmation interface.

        Args:
            header: The main title displayed at the top of the confirmation message.
            description: The body text explaining the action to be confirmed.
            footer: Optional footer text displayed in a muted style.
            variant: Visual style of the interface. Use ``"danger"`` for destructive
                actions and ``"default"`` for neutral confirmations.
            emojis: Whether to display emojis on the buttons.
                - ``False``: No emojis.
                - ``True``: Use default ❌ / ✅ emojis.
                - ``(cancel, confirm)``: Custom emoji strings.
            timeout: Time in seconds before the interface times out. Defaults to 900 seconds.
            user_id: If provided, only interactions from this user are accepted.
                Interactions from other users are ignored.
            yes_label: Label for the confirm button.
            no_label: Label for the cancel button.
            cancel_callback: Callback function to handle cancel button interactions.
            confirm_callback: Callback function to handle confirm button interactions.
        """
        self.header: str = header
        self.description: str = description
        self.footer: str | None = footer
        self.variant: Literal["default", "danger"] = variant
        self.user_id: int | None = user_id
        self.emojis: tuple[str | None, str | None] = (
            emojis if isinstance(emojis, tuple) else ("❌", "✅") if emojis else (None, None)
        )
        self.yes_label: str = yes_label
        self.no_label: str = no_label
        self._userset_cancel_callback: Callback[discord.Interaction] | None = cancel_callback
        self._userset_confirm_callback: Callback[discord.Interaction] | None = confirm_callback

        self._cancel_button: discord.ui.Button[Self] = discord.ui.Button(
            label=no_label,
            style=discord.ButtonStyle.secondary,
            emoji=self.emojis[0],
        )
        self._cancel_button.callback = self._cancel_btn_callback

        self._confirm_button: discord.ui.Button[Self] = discord.ui.Button(
            label=yes_label,
            style=discord.ButtonStyle.danger if variant == "danger" else discord.ButtonStyle.success,
            emoji=self.emojis[1],
        )
        self._confirm_button.callback = self._confirm_btn_callback

        texts: list[str] = [f"### {header}\n\n{description}"]
        if footer is not None:
            texts.append(f"-# {footer}")

        self._future: Future[tuple[bool, discord.Interaction]] = get_running_loop().create_future()

        super().__init__(
            discord.ui.Container(  # pyright: ignore[reportUnknownArgumentType]
                *[
                    discord.ui.TextDisplay(
                        text,
                    )
                    for text in texts
                ],
                color=discord.Color.red() if variant == "danger" else discord.Color.green(),
            ),
            discord.ui.ActionRow(  # pyright: ignore[reportUnknownArgumentType]
                self._cancel_button,
                self._confirm_button,
            ),
            timeout=timeout,
            disable_on_timeout=True,  # This is an opinionated decision
        )

    async def _confirm_btn_callback(self, interaction: discord.Interaction) -> None:
        if not interaction.user:
            msg = "Interaction user is None."
            raise TypeError(msg)

        if self.user_id is not None and interaction.user.id != self.user_id:
            return

        if self._userset_confirm_callback is not None:
            await maybe_awaitable(self._userset_confirm_callback(interaction))

        safe_set_future_result(self._future, value=(True, interaction))

    async def _cancel_btn_callback(self, interaction: discord.Interaction) -> None:
        if not interaction.user:
            msg = "Interaction user is None."
            raise TypeError(msg)

        if self.user_id is not None and interaction.user.id != self.user_id:
            return

        if self._userset_cancel_callback is not None:
            await maybe_awaitable(self._userset_cancel_callback(interaction))

        safe_set_future_result(self._future, value=(False, interaction))

    @override
    async def wait(self) -> tuple[bool, discord.Interaction]:  # pyright: ignore[reportIncompatibleMethodOverride]
        """Wait for the user to confirm or cancel the action.

        This coroutine blocks until the user interacts with the confirmation
        interface or the underlying Future is completed.

        Returns:
            Tuple containing:
                - True if the confirm button was pressed, False if the cancel button was pressed.
                - The interaction object that triggered the result.

        Raises:
            TimeoutError: If the interface times out before a user decision is made.
        """
        return await self._future

    async def finish(self) -> None:
        """Calls :meth:`stop` and awaits the result of :meth:`wait`."""
        await super().on_timeout()
        self.stop()

    @override
    async def on_timeout(self) -> None:
        safe_set_future_exception(self._future, TimeoutError("Timed out waiting for confirmation."))
        await self.finish()
