""" Refractor cog — /refractor status slash command. Displays a team's refractor progress: formula value vs next threshold with a progress bar, paginated 10 cards per page. Depends on WP-07 (evolution/cards API endpoint). """ import logging from typing import Optional import discord from discord import app_commands from discord.ext import commands from api_calls import db_get from helpers.main import get_team_by_owner logger = logging.getLogger("discord_app") PAGE_SIZE = 10 TIER_NAMES = { 0: "Base Chrome", 1: "Refractor", 2: "Gold Refractor", 3: "Superfractor", 4: "Superfractor", } FORMULA_LABELS = { "batter": "PA+TB×2", "sp": "IP+K", "rp": "IP+K", } def render_progress_bar(current: int, threshold: int, width: int = 10) -> str: """ Render a fixed-width ASCII progress bar. Examples: render_progress_bar(120, 149) -> '[========--]' render_progress_bar(0, 100) -> '[----------]' render_progress_bar(100, 100) -> '[==========]' """ if threshold <= 0: filled = width else: ratio = min(current / threshold, 1.0) filled = round(ratio * width) empty = width - filled return f"[{'=' * filled}{'-' * empty}]" def format_refractor_entry(card_state: dict) -> str: """ Format a single card state dict as a display string. Expected keys: player_name, card_type, current_tier, formula_value, next_threshold (None if fully evolved). Output example: **Mike Trout** (Refractor) [========--] 120/149 (PA+TB×2) — T1 → T2 """ player_name = card_state.get("player_name", "Unknown") card_type = card_state.get("card_type", "batter") current_tier = card_state.get("current_tier", 0) formula_value = card_state.get("formula_value", 0) next_threshold = card_state.get("next_threshold") tier_label = TIER_NAMES.get(current_tier, f"T{current_tier}") formula_label = FORMULA_LABELS.get(card_type, card_type) if current_tier >= 4 or next_threshold is None: bar = "[==========]" detail = "FULLY EVOLVED ★" else: bar = render_progress_bar(formula_value, next_threshold) detail = f"{formula_value}/{next_threshold} ({formula_label}) — T{current_tier} → T{current_tier + 1}" first_line = f"**{player_name}** ({tier_label})" second_line = f"{bar} {detail}" return f"{first_line}\n{second_line}" def apply_close_filter(card_states: list) -> list: """ Return only cards within 80% of their next tier threshold. Fully evolved cards (T4 or no next_threshold) are excluded. """ result = [] for state in card_states: current_tier = state.get("current_tier", 0) formula_value = state.get("formula_value", 0) next_threshold = state.get("next_threshold") if current_tier >= 4 or not next_threshold: continue if formula_value >= 0.8 * next_threshold: result.append(state) return result def paginate(items: list, page: int, page_size: int = PAGE_SIZE) -> tuple: """ Slice items for the given 1-indexed page. Returns (page_items, total_pages). Page is clamped to valid range. """ total_pages = max(1, (len(items) + page_size - 1) // page_size) page = max(1, min(page, total_pages)) start = (page - 1) * page_size return items[start : start + page_size], total_pages class Refractor(commands.Cog): """Refractor progress tracking slash commands.""" def __init__(self, bot): self.bot = bot refractor_group = app_commands.Group( name="refractor", description="Refractor tracking commands" ) @refractor_group.command( name="status", description="Show your team's refractor progress" ) @app_commands.describe( card_type="Card type filter (batter, sp, rp)", season="Season number (default: current)", tier="Filter by current tier (0-4)", progress='Use "close" to show cards within 80% of their next tier', page="Page number (default: 1, 10 cards per page)", ) async def refractor_status( self, interaction: discord.Interaction, card_type: Optional[str] = None, season: Optional[int] = None, tier: Optional[int] = None, progress: Optional[str] = None, page: int = 1, ): """Show a paginated view of the invoking user's team refractor progress.""" await interaction.response.defer(ephemeral=True) team = await get_team_by_owner(interaction.user.id) if not team: await interaction.edit_original_response( content="You don't have a team. Sign up with /newteam first." ) return params = [("team_id", team["id"])] if card_type: params.append(("card_type", card_type)) if season is not None: params.append(("season", season)) if tier is not None: params.append(("tier", tier)) data = await db_get("evolution/cards", params=params) if not data: await interaction.edit_original_response( content="No refractor data found for your team." ) return items = data if isinstance(data, list) else data.get("cards", []) if not items: await interaction.edit_original_response( content="No refractor data found for your team." ) return if progress == "close": items = apply_close_filter(items) if not items: await interaction.edit_original_response( content="No cards are currently close to a tier advancement." ) return page_items, total_pages = paginate(items, page) lines = [format_refractor_entry(state) for state in page_items] embed = discord.Embed( title=f"{team['sname']} Refractor Status", description="\n\n".join(lines), color=0x6F42C1, ) embed.set_footer(text=f"Page {page}/{total_pages} · {len(items)} card(s) total") await interaction.edit_original_response(embed=embed) async def setup(bot): await bot.add_cog(Refractor(bot))