import discord

from typing import List
from typing import Optional

MAX_SELECT_PER_PAGE = 2
MAX_OPTIONS_PER_SELECT = 25
ROLES_PER_PAGE = MAX_SELECT_PER_PAGE * MAX_OPTIONS_PER_SELECT


class RoleSelect(discord.ui.Select):
    def __init__(
        self,
        roles: List[discord.Role],
        selected_ids: set[int],
    ):
        options = []

        for role in roles:
            options.append(
                discord.SelectOption(
                    label=role.name, value=str(role.id), default=role.id in selected_ids
                )
            )

        super().__init__(
            placeholder="ロールを選択してください",
            min_values=0,
            max_values=len(options),
            options=options,
        )

    async def callback(self, interaction: discord.Interaction):
        view: RoleSelectView = self.view  # type: ignore

        # 一旦このSelectに含まれるロールを全て未選択扱いにする
        for option in self.options:
            view.selected_role_ids.discard(int(option.value))

        # 新しく選択されたものを追加
        for value in self.values:
            view.selected_role_ids.add(int(value))

        await interaction.response.defer()


class RoleSelectView(discord.ui.View):
    def __init__(
        self,
        interaction: discord.Interaction,
        roles: List[discord.Role],
        default_roles: Optional[List[discord.Role]] = None,
        extra_items: Optional[List[discord.ui.Item]] = None,
    ):
        super().__init__(timeout=None)

        self.interaction = interaction
        self.roles = roles
        self.page = 0

        self.selected_role_ids: set[int] = {role.id for role in (default_roles or [])}

        self.extra_items = extra_items

        if extra_items:
            for item in extra_items:
                self.add_item(item)

        self._refresh_items()

    def _refresh_items(self):
        # Selectとページ移動ボタンだけを一旦削除
        self.clear_items()

        start = self.page * ROLES_PER_PAGE
        end = start + ROLES_PER_PAGE
        page_roles = self.roles[start:end]

        for i in range(0, len(page_roles), MAX_OPTIONS_PER_SELECT):
            self.add_item(
                RoleSelect(
                    roles=page_roles[i : i + MAX_OPTIONS_PER_SELECT],
                    selected_ids=self.selected_role_ids,
                )
            )

        self.add_item(PrevButton())
        self.add_item(NextButton())

        extra_items = self.extra_items
        if extra_items:
            for item in extra_items:
                self.add_item(item)

    def get_selected_roles(self) -> List[discord.Role]:
        guild = self.interaction.guild
        if not guild:
            return []

        return [
            guild.get_role(role_id)
            for role_id in self.selected_role_ids
            if guild.get_role(role_id)
        ]


class PrevButton(discord.ui.Button):
    def __init__(self):
        super().__init__(label="前へ", style=discord.ButtonStyle.secondary)

    async def callback(self, interaction: discord.Interaction):
        view: RoleSelectView = self.view  # type: ignore

        if view.page > 0:
            view.page -= 1
            view._refresh_items()

        await interaction.response.edit_message(view=view)


class NextButton(discord.ui.Button):
    def __init__(self):
        super().__init__(label="次へ", style=discord.ButtonStyle.secondary)

    async def callback(self, interaction: discord.Interaction):
        view: RoleSelectView = self.view  # type: ignore

        max_page = (len(view.roles) - 1) // ROLES_PER_PAGE
        if view.page < max_page:
            view.page += 1
            view._refresh_items()

        await interaction.response.edit_message(view=view)


class RoleSelectPagination:
    def __init__(
        self,
        interaction: discord.Interaction,
        edit: bool = False,
        default_values: Optional[List[discord.Role]] = None,
        values: Optional[List[discord.Role]] = None,
        extra_items: Optional[List[discord.ui.Item]] = None,
    ):
        self.interaction = interaction
        self.edit = edit

        guild = interaction.guild
        if not guild:
            raise RuntimeError("Guild only")

        self.roles = sorted(
            values if values and len(values) > 0 else list(guild.roles),
            key=lambda role: role.position,
            reverse=True,
        )
        self.view = RoleSelectView(
            interaction=interaction,
            roles=self.roles,
            default_roles=default_values,
            extra_items=extra_items,
        )

    async def send(self, content: str = None, embed: discord.Embed = None):
        if self.edit:
            await self.interaction.response.edit_message(
                content=content, embed=embed, view=self.view
            )
        else:
            await self.interaction.response.send_message(
                content=content, embed=embed, view=self.view
            )

    def get_selected_roles(self) -> List[discord.Role]:
        return self.view.get_selected_roles()
