# 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 discord import Interaction
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 (int): 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.

    Updates the parent view's page index and refreshes the UI when clicked.

    Attributes:
        page (int): 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 discord.ui.Button
                (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.

        Raises:
            TypeError: If the button is not attached to a view.
        """
        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 PaginatorIndicatorModal(discord.ui.DesignerModal):
    """Modal for direct page navigation input.

    Allows users to jump to a specific page by entering a page number.

    Attributes:
        view (PaginatorInterfaceBase): The parent paginator view.
        input (discord.ui.TextInput): The text input field for page number.
        invalid (str): Error message shown for invalid page numbers.
    """

    def __init__(
        self, modal_title: str, label_title: str, label_description: str, invalid: str, view: PaginatorInterfaceBase
    ) -> None:
        """Initialize the page navigation modal.

        Args:
            modal_title: The title displayed at the top of the modal.
            label_title: The label text for the input field.
            label_description: The description text for the input field.
            invalid: Error message shown when an invalid page number is entered.
            view: The parent paginator view.
        """
        self.view: PaginatorInterfaceBase = view
        self.input: discord.ui.TextInput = discord.ui.TextInput(
            style=discord.InputTextStyle.short, max_length=len(str(view.max_page))
        )
        self.invalid: str = invalid
        label: discord.ui.Label[Self] = discord.ui.Label(
            label=label_title, description=label_description, item=self.input
        )
        super().__init__(label, title=modal_title)

    @override
    async def callback(self, interaction: Interaction) -> None:
        """Handle modal submission.

        Validates the page number and navigates to the specified page if valid.

        Args:
            interaction: The interaction object from the modal submission.
        """
        await interaction.response.defer()
        page = self.input.value
        if not page or not page.isdigit() or (int_page := int(page) - 1) > self.view.max_page:
            await interaction.respond(self.invalid, ephemeral=True)
            return
        self.view.page = int_page
        await maybe_awaitable(self.view.update())
        await interaction.edit(view=self.view)


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

    Shows the current page position in the format "X/Y". Can optionally be
    made interactable to open a modal for direct page navigation.

    Attributes:
        disabled (bool): Whether the button is disabled.
        modal_title (str): Title for the page navigation modal.
        label_title (str): Label title for the modal input field.
        label_description (str): Description for the modal input field.
        invalid (str): Error message for invalid page input.
    """

    def __init__(
        self,
        *,
        label: str,
        interactable: bool,
        modal_title: str,
        label_title: str,
        label_description: str,
        invalid: str,
    ) -> None:
        """Initialize a page indicator button.

        Args:
            label: The text to display on the button (typically "X/Y" format).
            interactable: Whether clicking the button should open a navigation modal.
            modal_title: Title for the page navigation modal.
            label_title: Label for the modal's input field.
            label_description: Description for the modal's input field.
            invalid: Error message shown for invalid page numbers.
        """
        super().__init__(style=discord.ButtonStyle.secondary, label=label)
        self.disabled: bool = not interactable
        self.modal_title: str = modal_title
        self.label_title: str = label_title
        self.label_description: str = label_description
        self.invalid: str = invalid

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

        Opens a modal for direct page navigation when clicked.

        Args:
            interaction: The interaction object from the button click.

        Raises:
            TypeError: If the button is not attached to a view.
        """
        if self.view is None:
            msg = "This button is not attached to a view."
            raise TypeError(msg)

        await interaction.response.send_modal(
            PaginatorIndicatorModal(  # pyright: ignore[reportArgumentType]
                modal_title=self.modal_title,
                label_title=self.label_title,
                label_description=self.label_description,
                invalid=self.invalid,
                view=self.view,
            )
        )


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

    Provides five navigation elements: first page, previous page, page indicator,
    next page, and last page. Buttons are automatically disabled when they would
    navigate beyond valid page boundaries.

    Class Attributes:
        FIRST_EMOJI (EmojiType): Emoji for the "first page" button.
        LEFT_EMOJI (EmojiType): Emoji for the "previous page" button.
        RIGHT_EMOJI (EmojiType): Emoji for the "next page" button.
        LAST_EMOJI (EmojiType): Emoji for the "last page" button.
        INTERACTABLE_INDICATOR (bool): Whether the page indicator opens a navigation modal.
        MODAL_TITLE (str): Title for the page navigation modal.
        MODAL_LABEL_TITLE (str): Label for the modal's input field.
        MODAL_LABEL_DESCRIPTION (str): Description for the modal's input field.
        MODAL_INVALID (str): Error message for invalid page input.
    """

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

    INTERACTABLE_INDICATOR: bool = True
    MODAL_TITLE: str = "Page Navigation"
    MODAL_LABEL_TITLE: str = "Page Number:"
    MODAL_LABEL_DESCRIPTION: str = "Input a page number to jump to that page."
    MODAL_INVALID: str = "Please input a valid page number."

    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}",
                interactable=self.INTERACTABLE_INDICATOR,
                modal_title=self.MODAL_TITLE,
                label_title=self.MODAL_LABEL_TITLE,
                label_description=self.MODAL_LABEL_DESCRIPTION,
                invalid=self.MODAL_INVALID,
            ),
        )
        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.

    Manages navigation between multiple pages of UI components, automatically
    displaying the appropriate page content and navigation controls. Each page
    is dynamically loaded when navigating between pages.

    Attributes:
        pages (Sequence[Sequence[discord.ui.ViewItem[discord.ui.DesignerView]]]):
            The sequence of pages, where each page is a sequence of view items.
        page (int): The current page index (0-based).
        controls (type[PaginatorControlsBase[Self]]): 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.
        """
        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)
        if len(self.pages) > 1:
            self.add_item(self.controls(self))


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