text: from dataclasses import dataclass
  from typing import List, Optional, Type
  
  from discord import AllowedMentions, Member, Message, Permissions, Role
  
  from commanderbot.ext.roles.roles_result import (
      AddableRolesResult,
      JoinableRolesResult,
      LeavableRolesResult,
      RemovableRolesResult,
      RolesResult,
  )
  from commanderbot.ext.roles.roles_store import RoleEntry, RoleEntryPair, RolesStore
  from commanderbot.lib import CogGuildState, GuildContext, MemberContext, RoleID, RoleSet
  from commanderbot.lib.dialogs import ConfirmationResult, confirm_with_reaction
  
  SAFE_PERMS = Permissions.none()
  # general
  SAFE_PERMS.read_messages = True
  # membership
  SAFE_PERMS.change_nickname = True
  # text channel
  SAFE_PERMS.send_messages = True
  SAFE_PERMS.use_threads = True
  SAFE_PERMS.embed_links = True
  SAFE_PERMS.attach_files = True
  SAFE_PERMS.add_reactions = True
  SAFE_PERMS.external_emojis = True
  SAFE_PERMS.read_message_history = True
  # voice channel
  SAFE_PERMS.connect = True
  SAFE_PERMS.speak = True
  SAFE_PERMS.stream = True
  SAFE_PERMS.use_voice_activation = True
  
  ROLE_TIPS = "\n".join(
      [
          ">>> Tips for providing roles:",
          "- You can provide multiple roles in one command",
          "- Use a word that uniquely identifies each role",
          "- If multiple words are required to specify a role, surround them with quotes",
          "- If all else fails, use the role's ID",
      ]
  )
  
  
  @dataclass
  class RolesGuildState(CogGuildState):
      """
      Encapsulates the state and logic of the roles cog, at the guild level.
  
      Attributes
      -----------
      store
          The store used to interface with persistent data in a database-agnostic way.
      """
  
      store: RolesStore
  
      async def reply(self, ctx: GuildContext, content: str) -> Message:
          """Wraps `Context.reply()` with some extension-default boilerplate."""
          return await ctx.message.reply(
              content,
              allowed_mentions=AllowedMentions.none(),
          )
  
      def sort_role_pairs(self, role_pairs: List[RoleEntryPair]) -> List[RoleEntryPair]:
          # Sort by stringified role name.
          return sorted(role_pairs, key=lambda role_pair: str(role_pair[0]))
  
      def stringify_role_pairs(self, role_pairs: List[RoleEntryPair]) -> str:
          lines = []
          for role, role_entry in role_pairs:
              # Start with the role mention.
              role_line_parts = [f"{role.mention}"]
              # Mention that it's not joinable, if applicable.
              if not role_entry.joinable:
                  role_line_parts.append(" (not joinable)")
              # Mention that it's not leavable, if applicable.
              if not role_entry.leavable:
                  role_line_parts.append(" (not leavable)")
              # Add its description, if any.
              if role_entry.description:
                  role_line_parts.append(f" {role_entry.description}")
              role_line = "".join(role_line_parts)
              lines.append(role_line)
          return "\n".join(lines)
  
      async def resolve_role(self, role_id: RoleID) -> Optional[Role]:
          # Attempt to resolve the role from the given ID.
          role = self.guild.get_role(role_id)
          # If the role resolves correctly, return it.
          if isinstance(role, Role):
              return role
          # Otherwise, warn about and automatically clean-up unresolved roles.
          self.log.warning(f"Cleaning-up unresolved role with ID: {role_id}")
          await self.store.deregister_role_by_id(self.guild.id, role_id)
  
      async def get_all_role_pairs(self) -> List[RoleEntryPair]:
          # Flatten the full list of role entry pairs.
          role_pairs: List[RoleEntryPair] = []
          role_entries = await self.store.get_all_role_entries(self.guild)
          for role_entry in role_entries:
              # If the role can be resolved, add it to the list.
              if role := await self.resolve_role(role_entry.role_id):
                  role_pairs.append((role, role_entry))
          # Sort and return.
          return self.sort_role_pairs(role_pairs)
  
      async def get_relevant_role_for(
          self, role_entry: RoleEntry, member: Member
      ) -> Optional[Role]:
          # If the role can't be resolved, it certainly isn't relevant.
          if role := await self.resolve_role(role_entry.role_id):
              # A role is relevant to the user if:
              # 1. it's joinable; or
              # 2. it's leavable and present on the user.
              if role_entry.joinable or (role_entry.leavable and (role in member.roles)):
                  return role
  
      async def get_relevant_role_pairs(self, member: Member) -> List[RoleEntryPair]:
          # Build a list of relevant role entry pairs.
          role_pairs: List[RoleEntryPair] = []
          role_entries = await self.store.get_all_role_entries(self.guild)
          for role_entry in role_entries:
              # If the role is relevant to the user, add it to the list.
              if role := await self.get_relevant_role_for(role_entry, member):
                  role_pairs.append((role, role_entry))
          # Sort and return.
          return self.sort_role_pairs(role_pairs)
  
      async def get_unsafe_role_perms(self, role: Role) -> Optional[Permissions]:
          # Start with an empty set of unsafe permissions.
          unsafe_perms = Permissions.none()
          # Collect all of the enabled permissions.
          unsafe_pnames = [pname for (pname, pvalue) in role.permissions if pvalue]
          for pname in unsafe_pnames:
              # If this permission is not safe, add it to the set of unsafe permissions.
              if not getattr(SAFE_PERMS, pname):
                  setattr(unsafe_perms, pname, True)
          # Only return a [Permissions] object if there's actually something in it.
          if unsafe_perms != Permissions.none():
              return unsafe_perms
  
      async def should_register_role(
          self, ctx: GuildContext, role: Role
      ) -> ConfirmationResult:
          # If the role contains unsafe permissions, ask for confirmation.
          if unsafe_perms := await self.get_unsafe_role_perms(role):
              unsafe_perms_str = "\n".join(
                  f"- `{pname}`" for pname, pvalue in unsafe_perms if pvalue
              )
              conf_content = "\n".join(
                  [
                      f"{role.mention} contains potentially unsafe permissions:",
                      unsafe_perms_str,
                      f"Do you want to register {role.mention} anyway?",
                  ]
              )
              return await confirm_with_reaction(self.bot, ctx, conf_content)
          # Otherwise, the role can be registered right away.
          return ConfirmationResult.YES
  
      async def register_role(
          self,
          ctx: GuildContext,
          role: Role,
          joinable: bool,
          leavable: bool,
          description: Optional[str],
      ):
          # Check whether this is a role that should be registered. If the role
          # contains unsafe permissions, this will also ask for confirmation.
          conf = await self.should_register_role(ctx, role)
          # If the answer was yes...
          if conf == ConfirmationResult.YES:
              # Attempt to register the role.
              role_entry = await self.store.register_role(
                  role,
                  joinable=joinable,
                  leavable=leavable,
                  description=description,
              )
              # Send a response that includes some confirmation about the flags.
              if role_entry.joinable and role_entry.leavable:
                  await self.reply(
                      ctx,
                      f"✅ {role.mention} has been registered as opt-in/opt-out.",
                  )
              elif role_entry.joinable:
                  await self.reply(
                      ctx,
                      f"✅ {role.mention} has been registered as"
                      + " **opt-in only** (not leavable).",
                  )
              elif role_entry.leavable:
                  await self.reply(
                      ctx,
                      f"✅ {role.mention} has been registered as"
                      + " **opt-out only** (not joinable).",
                  )
              else:
                  await self.reply(
                      ctx,
                      f"✅ {role.mention} has been registered, but"
                      + " is **neither opt-in nor opt-out**.",
                  )
          # If the answer was no, send a response.
          if conf == ConfirmationResult.NO:
              await self.reply(ctx, f"❌ {role.mention} has **not** been registered.")
          # If no answer was provided, don't do anything.
  
      async def deregister_role(self, ctx: GuildContext, role: Role):
          await self.store.deregister_role(role)
          await self.reply(ctx, f"✅ {role.mention} has been deregistered.")
  
      async def show_all_roles(self, ctx: GuildContext):
          if role_pairs := await self.get_all_role_pairs():
              role_pairs_str = self.stringify_role_pairs(role_pairs)
              await self.reply(
                  ctx,
                  f"There are {len(role_pairs)} roles registered:\n{role_pairs_str}\n{ROLE_TIPS}",
              )
          else:
              await self.reply(ctx, f"🤷 There are no roles registered.")
  
      async def show_relevant_roles(self, ctx: MemberContext):
          # List only roles that are relevant to this user.
          member = ctx.author
          if role_pairs := await self.get_relevant_role_pairs(member):
              role_pairs_str = self.stringify_role_pairs(role_pairs)
              await self.reply(
                  ctx,
                  f"There are {len(role_pairs)} roles relevant to you:\n{role_pairs_str}\n{ROLE_TIPS}",
              )
          else:
              await self.reply(ctx, f"🤷 There are no roles relevant to you.")
  
      async def join_roles(self, ctx: MemberContext, roles: List[Role]):
          await self.join_leave_roles(ctx, roles, JoinableRolesResult)
  
      async def leave_roles(self, ctx: MemberContext, roles: List[Role]):
          await self.join_leave_roles(ctx, roles, LeavableRolesResult)
  
      async def join_leave_roles(
          self, ctx: MemberContext, roles: List[Role], result_type: Type[RolesResult]
      ):
          if not roles:
              await self.reply(ctx, "🤔 You didn't provide any roles.")
              return
          member = ctx.author
          result = await result_type.build(self.store, member, roles)
          lines = await result.apply()
          content = "\n".join(lines)
          await self.reply(ctx, content)
  
      async def add_roles_to_members(
          self, ctx: MemberContext, roles: List[Role], members: List[Member]
      ):
          await self.add_remove_roles(ctx, roles, members, AddableRolesResult)
  
      async def remove_roles_from_members(
          self, ctx: MemberContext, roles: List[Role], members: List[Member]
      ):
          await self.add_remove_roles(ctx, roles, members, RemovableRolesResult)
  
      async def add_remove_roles(
          self,
          ctx: MemberContext,
          roles: List[Role],
          members: List[Member],
          result_type: Type[RolesResult],
      ):
          if not roles:
              await self.reply(ctx, "🤔 You didn't provide any roles.")
              return
          if not members:
              await self.reply(ctx, "🤔 You didn't provide any members.")
              return
          acting_user = ctx.author
          results = [
              await result_type.build(self.store, member, roles, acting_user)
              for member in members
          ]
          lines = []
          for result in results:
              result_lines = await result.apply()
              lines += result_lines
          content = "\n".join(lines)
          await self.reply(ctx, content)
  
      async def show_permitted_roles(self, ctx: GuildContext):
          permitted_roles = await self.store.get_permitted_roles(self.guild)
          if permitted_roles is not None:
              count_permitted_roles = len(permitted_roles)
              role_mentions = permitted_roles.to_mentions(self.guild)
              await self.reply(
                  ctx,
                  f"There are {count_permitted_roles} roles permitted to"
                  + f" add/remove other users to/from roles: {role_mentions}",
              )
          else:
              await self.reply(
                  ctx,
                  f"No roles are permitted to add/remove other users to/from roles.",
              )
  
      async def set_permitted_roles(self, ctx: GuildContext, *roles: Role):
          if not roles:
              await self.reply(ctx, "🤷 No roles provided.")
              return
          new_permitted_roles = RoleSet(set(role.id for role in roles))
          new_role_mentions = new_permitted_roles.to_mentions(self.guild)
          old_permitted_roles = await self.store.set_permitted_roles(
              self.guild, new_permitted_roles
          )
          if old_permitted_roles is not None:
              old_role_mentions = old_permitted_roles.to_mentions(self.guild)
              await self.reply(
                  ctx,
                  f"✅ Changed permitted roles from {old_role_mentions}"
                  + f" to: {new_role_mentions}",
              )
          else:
              await self.reply(ctx, f"✅ Changed permitted roles to: {new_role_mentions}")
  
      async def clear_permitted_roles(self, ctx: GuildContext):
          old_permitted_roles = await self.store.set_permitted_roles(self.guild, None)
          if old_permitted_roles is not None:
              role_mentions = old_permitted_roles.to_mentions(self.guild)
              await self.reply(ctx, f"✅ Cleared all permitted roles: {role_mentions}")
          else:
              await self.reply(
                  ctx,
                  f"🤷 No roles are permitted to add/remove other users to/from roles.",
              )
  
      async def member_has_permission(self, member: Member) -> bool:
          permitted_roles = await self.store.get_permitted_roles(self.guild)
          if permitted_roles is None:
              return False
          has_permission = permitted_roles.member_has_some(member)
          return has_permission
  