import discord
from typing import List, Union, Optional

PageType = Union[str, discord.Embed]


class Paginator(discord.ui.View):
    def __init__(
        self,
        pages: List[PageType],
        *,
        author: discord.abc.User,
        index: int = 0,
        timeout: float = 180,
        loop: bool = False,
        ephemeral: bool = False,
        extra_items: Optional[List[type[discord.ui.Item]]] = None,
        all_disable: bool = False,
    ):
        super().__init__(timeout=timeout)

        if not pages:
            raise ValueError("pages must not be empty")

        self.pages = pages
        self.author = author
        self.index = index
        self.loop = loop
        self.ephemeral = ephemeral
        self.timeout_value = timeout
        self.extra_item_classes = extra_items or []
        self.all_disable = all_disable

        self._interaction: discord.Interaction | None = None
        self.message: discord.Message | None = None

        self._build()

    # =====================
    # build
    # =====================

    def _build(self):
        self._add_nav()
        self._add_extra()

    def _add_nav(self):
        max_index = len(self.pages) - 1

        self.first = discord.ui.Button(
            label="⏮",
            style=discord.ButtonStyle.secondary,
            disabled=((not self.loop and self.index == 0) or self.all_disable),
        )
        self.prev = discord.ui.Button(
            label="◀",
            style=discord.ButtonStyle.secondary,
            disabled=((not self.loop and self.index == 0) or self.all_disable),
        )
        self.counter = discord.ui.Button(
            label=f"{self.index + 1}/{len(self.pages)}",
            style=discord.ButtonStyle.gray,
            disabled=True,
        )
        self.next = discord.ui.Button(
            label="▶",
            style=discord.ButtonStyle.secondary,
            disabled=((not self.loop and self.index == max_index) or self.all_disable),
        )
        self.last = discord.ui.Button(
            label="⏭",
            style=discord.ButtonStyle.secondary,
            disabled=((not self.loop and self.index == max_index) or self.all_disable),
        )

        self.first.callback = self._go_first
        self.prev.callback = self._go_prev
        self.next.callback = self._go_next
        self.last.callback = self._go_last

        for item in (self.first, self.prev, self.counter, self.next, self.last):
            self.add_item(item)

    def _add_extra(self):
        for cls in self.extra_item_classes:
            self.add_item(cls(self))

    # =====================
    # helpers
    # =====================

    def payload(self):
        page = self.pages[self.index]
        if isinstance(page, discord.Embed):
            return {"embed": page, "content": None}
        return {"content": page, "embed": None}

    async def interaction_check(self, interaction: discord.Interaction) -> bool:
        if interaction.user.id != self.author.id:
            await interaction.response.send_message(
                "この操作は実行できません。",
                ephemeral=True,
            )
            return False
        return True

    # =====================
    # recreate (timeout reset)
    # =====================

    async def _recreate(self, interaction: discord.Interaction, new_index: int):
        new_view = Paginator(
            pages=self.pages,
            author=self.author,
            index=new_index,
            timeout=self.timeout_value,
            loop=self.loop,
            ephemeral=self.ephemeral,
            extra_items=self.extra_item_classes,
        )

        new_view._interaction = interaction

        await interaction.response.edit_message(
            **new_view.payload(),
            view=new_view,
        )

        try:
            new_view.message = await interaction.original_response()
        except Exception:
            pass

    # =====================
    # navigation callbacks
    # =====================

    async def _go_first(self, interaction: discord.Interaction):
        if self.index == 0:
            return
        await self._recreate(interaction, 0)

    async def _go_prev(self, interaction: discord.Interaction):
        if self.index == 0:
            return
        await self._recreate(interaction, self.index - 1)

    async def _go_next(self, interaction: discord.Interaction):
        if self.index == len(self.pages) - 1:
            return
        await self._recreate(interaction, self.index + 1)

    async def _go_last(self, interaction: discord.Interaction):
        if self.index == len(self.pages) - 1:
            return
        await self._recreate(interaction, len(self.pages) - 1)

    # =====================
    # timeout
    # =====================

    async def on_timeout(self):
        # ephemeral → delete
        if self.ephemeral and self._interaction:
            try:
                await self._interaction.delete_original_response()
            except Exception:
                pass
            return

        # normal message → disable buttons
        for item in self.children:
            item.disabled = True

        try:
            if self.message:
                await self.message.edit(view=self)
        except Exception:
            pass
