# SPDX-License-Identifier: MIT
# Copyright: 2026 plun1331, NiceBots.xyz, DiscordCN Contributors
# Source (modified): https://gist.github.com/plun1331/d8f375922ee7a98c2961530f946ae8ef

"""Paginator interface module."""

# pyright: reportUnknownMemberType=false

from abc import ABC, abstractmethod
from collections.abc import Sequence
from typing import Any, Generic, TypeVar

import discord
from typing_extensions import Self, override

from discordcn.pycord._utils import maybe_awaitable

from ._types import EmojiType

T = TypeVar("T", bound="PaginatorInterfaceBase")


class PaginatorInterfaceBase(ABC, discord.ui.DesignerView):
    """ABC defining the interface for paginator views.

    This ABC specifies the minimum requirements for a view to be
    compatible with paginator controls, ensuring consistent behavior across
    different paginator implementations.

    Attributes:
        page: The current page index (0-based).
    """

    page: int

    @property
    @abstractmethod
    def max_page(self) -> int:
        """The maximum page index (0-based)."""
        ...

    @abstractmethod
    def update(self) -> None:
        """Update the view to reflect the current page state."""
        ...


class PaginatorControlsBase(ABC, discord.ui.ActionRow[T], Generic[T]):
    """ABC defining the interface for paginator controls.

    This ABC specifies the minimum requirements for a paginator controls
    component, ensuring consistent behavior across different implementations.
    """

    @abstractmethod
    def __init__(self, view: PaginatorInterfaceBase) -> None:
        """Initialize paginator controls.

        Args:
            view: The parent paginator view implementing PaginatorInterfaceBase.
        """
        ...


class PageButton(discord.ui.Button[PaginatorInterfaceBase]):
    """Navigation button for jumping to a specific page.

    This button updates the parent view's page index and refreshes the UI
    when clicked. It is designed to work with any view implementing
    PaginatorInterfaceBase.

    Attributes:
        page: The target page index this button navigates to.
    """

    def __init__(self, page: int, **kwargs: Any) -> None:
        """Initialize a page navigation button.

        Args:
            page: The target page index (0-based) to navigate to when clicked.
            **kwargs: Additional keyword arguments passed to the parent
                discord.ui.Button constructor (e.g., ``emoji``, ``style``,
                ``disabled``).
        """
        super().__init__(**kwargs)
        self.page: int = page

    @override
    async def callback(self, interaction: discord.Interaction) -> None:
        """Handle button click interactions.

        Updates the parent view's page index, triggers a view update, and
        edits the message to reflect the new page state.

        Args:
            interaction: The interaction object from the button click.
        """
        if self.view is None:
            msg = "This button is not attached to a view."
            raise TypeError(msg)
        v = self.view
        v.page = self.page
        await maybe_awaitable(v.update())
        if interaction.response.is_done():
            await interaction.edit_original_response(view=v)
        else:
            await interaction.response.edit_message(view=v)


class PageIndicatorButton(discord.ui.Button[PaginatorInterfaceBase]):
    """Non-interactive button displaying the current page number.

    This button serves as a visual indicator of the current page position
    and is always disabled to prevent interaction. It displays text in the
    format "Page X/Y".
    """

    def __init__(self, *, label: str) -> None:
        """Initialize a page indicator button.

        Args:
            label: The text to display on the button (typically "Page X/Y").
        """
        super().__init__(style=discord.ButtonStyle.secondary, label=label)
        self.disabled: bool = True

    @override
    async def callback(self, interaction: discord.Interaction) -> None:
        """Handle button click interactions (no-op).

        This callback defers the interaction invisibly since the button is
        disabled and should never be clicked.

        Args:
            interaction: The interaction object from the button click.
        """
        await interaction.response.defer(invisible=True)


class PaginatorControls(PaginatorControlsBase[T], Generic[T]):
    """Navigation controls row for paginator interfaces.

    This action row contains five elements:
        - First page button (⏮️): Jump to page 0
        - Previous page button (◀️): Go back one page
        - Page indicator: Display current page position (non-interactive)
        - Next page button (▶️): Advance one page
        - Last page button (⏭️): Jump to the final page

    Buttons are automatically disabled when they would navigate beyond valid
    page boundaries (e.g., previous/first buttons on page 0).

    Attributes:
        FIRST_EMOJI: The emoji to use for the "first page" button.
        LEFT_EMOJI: The emoji to use for the "previous page" button.
        RIGHT_EMOJI: The emoji to use for the "next page" button.
        LAST_EMOJI: The emoji to use for the "last page" button.
    """

    FIRST_EMOJI: EmojiType = discord.PartialEmoji(name="⏮️")
    LEFT_EMOJI: EmojiType = discord.PartialEmoji(name="◀️")
    RIGHT_EMOJI: EmojiType = discord.PartialEmoji(name="▶️")
    LAST_EMOJI: EmojiType = discord.PartialEmoji(name="⏭️")

    def __init__(
        self,
        view: T,
    ) -> None:
        """Initialize paginator navigation controls.

        Creates a row of navigation buttons with appropriate enabled/disabled
        states based on the current page position.

        Args:
            view: The parent paginator view implementing PaginatorInterfaceBase.
        """
        discord.ui.ActionRow.__init__(self)
        self.add_item(
            PageButton(
                0,
                emoji=self.FIRST_EMOJI,
                style=discord.ButtonStyle.primary,
                disabled=view.page == 0,
            ),
        )
        self.add_item(
            PageButton(
                view.page - 1,
                emoji=self.LEFT_EMOJI,
                style=discord.ButtonStyle.secondary,
                disabled=view.page == 0,
            )
        )
        self.add_item(
            PageIndicatorButton(
                label=f"{view.page + 1}/{view.max_page + 1}",
            ),
        )
        self.add_item(
            PageButton(
                view.page + 1,
                emoji=self.RIGHT_EMOJI,
                style=discord.ButtonStyle.secondary,
                disabled=view.page == view.max_page,
            )
        )
        self.add_item(
            PageButton(
                view.max_page,
                emoji=self.LAST_EMOJI,
                style=discord.ButtonStyle.primary,
                disabled=view.page == view.max_page,
            )
        )


class PaginatorInterface(PaginatorInterfaceBase):
    """Multi-page view with automatic navigation controls.

    This view manages navigation between multiple pages of UI components,
    automatically displaying the appropriate page content and navigation
    controls. Each page is defined as a sequence of view items (buttons,
    selects, etc.) that are dynamically loaded when navigating between pages.

    The paginator automatically appends navigation controls to the bottom of
    each page, allowing users to move between pages using first/previous/next/last
    buttons and see their current position.

    Attributes:
        pages: The sequence of pages, where each page is a sequence of view items.
        page: The current page index (0-based).
        controls: The paginator controls class to use for navigation.
    """

    def __init__(
        self,
        pages: Sequence[Sequence[discord.ui.ViewItem[discord.ui.DesignerView]]],
        controls: type[PaginatorControlsBase[Self]] = PaginatorControls,
    ) -> None:
        """Initialize a paginator view.

        Args:
            pages: A sequence of pages, where each page is a sequence of view items
                (buttons, selects, etc.) to display on that page. Navigation controls
                are automatically added to each page.
            controls: The paginator controls class to use for navigation. Defaults
                to PaginatorControls.
        """
        super().__init__(timeout=300)
        self.pages: Sequence[Sequence[discord.ui.ViewItem[discord.ui.DesignerView]]] = pages
        self.page: int = 0
        self.controls: type[PaginatorControlsBase[Self]] = controls
        self.update()

    @property
    @override
    def max_page(self) -> int:
        """The maximum page index (0-based).

        Returns:
            The index of the last page, equal to ``len(pages) - 1``.
        """
        return len(self.pages) - 1

    @override
    def update(self) -> None:
        """Update the view to display the current page.

        Clears all existing items, adds items from the current page, and
        appends navigation controls. This method is called automatically
        when navigating between pages.
        """
        self.clear_items()
        for item in self.pages[self.page]:
            self.add_item(item)
        self.add_item(self.controls(self))


__all__ = (
    "PageButton",
    "PageIndicatorButton",
    "PaginatorControls",
    "PaginatorControlsBase",
    "PaginatorInterface",
    "PaginatorInterfaceBase",
)
