"""Help and command listing commands."""

from typing import TYPE_CHECKING, cast

from mudlib.commands import CommandDefinition, _registry, register, resolve_prefix
from mudlib.commands.movement import DIRECTIONS
from mudlib.player import Player

if TYPE_CHECKING:
    from mudlib.combat.moves import CombatMove


async def _show_command_detail(player: Player, command_name: str) -> None:
    """Show detailed information about a specific command.

    Args:
        player: The player requesting details
        command_name: Name or alias of the command to show
    """
    from mudlib.combat.commands import combat_moves

    # Resolve command by exact match or prefix
    result = resolve_prefix(command_name)

    if isinstance(result, list):
        # Multiple matches - show disambiguation
        names = result
        if len(names) == 2:
            msg = f"{names[0]} or {names[1]}?\r\n"
        else:
            prefix = ", ".join(cast(list[str], names[:-1]))
            msg = f"{prefix}, or {names[-1]}?\r\n"
        await player.send(msg)
        return
    else:
        defn = result

    # If still not in registry, check if it's a combat move name (like "punch left")
    if defn is None:
        move = combat_moves.get(command_name)
        if move is not None:
            # Create a synthetic defn for display purposes
            defn = CommandDefinition(
                name=move.name,
                handler=lambda p, a: None,
                aliases=move.aliases,
                mode="*",
                help="",
            )
            await _show_single_command(player, defn, move)
            return

        await player.send(f"Unknown command: {command_name}\r\n")
        return

    # Try to get combat move data
    move = combat_moves.get(defn.name)

    # Check if this is a variant base command (like "punch")
    # by looking for variants in combat_moves
    variants = []
    if move is None:
        for _move_name, m in combat_moves.items():
            if m.command == defn.name and m.variant:
                variants.append(m)

    # If we found variants, show variant overview
    if variants:
        await _show_variant_overview(player, defn, variants)
        return

    # Otherwise show single command detail
    await _show_single_command(player, defn, move)


async def _show_single_command(
    player: Player, defn: CommandDefinition, move: "CombatMove | None"
) -> None:
    """Show details for a single command or combat move.

    Args:
        player: The player requesting details
        defn: Command definition
        move: Combat move data if available
    """

    lines = [defn.name]

    # Check if move is locked
    if move is not None and move.unlock_condition:
        base = move.command or move.name
        if base not in player.unlocked_moves:
            cond = move.unlock_condition
            if cond.type == "kill_count":
                lock_msg = f"[LOCKED] Defeat {cond.threshold} enemies to unlock."
            elif cond.type == "mob_kills":
                mob = cond.mob_name
                lock_msg = f"[LOCKED] Defeat {cond.threshold} {mob}s to unlock."
            else:
                lock_msg = "[LOCKED]"
            lines.append(f"  {lock_msg}")

    # Show description first for combat moves (most important context)
    if move is not None and move.description:
        lines.append(f"  {move.description}")

    # Always show aliases
    if defn.aliases:
        aliases_str = ", ".join(defn.aliases)
        lines.append(f"  aliases: {aliases_str}")

    # Show type for combat moves
    if move is not None:
        lines.append(f"  type: {move.move_type}")

    # Show mode for non-wildcard modes
    if defn.mode != "*":
        lines.append(f"  mode: {defn.mode}")

    # Combat move specific details
    if move is not None:
        lines.append(f"  stamina: {move.stamina_cost}")
        lines.append(f"  timing window: {move.timing_window_ms}ms")
        if move.damage_pct > 0:
            damage_pct = int(move.damage_pct * 100)
            lines.append(f"  damage: {damage_pct}%")
        if move.telegraph:
            lines.append(f"  telegraph: {move.telegraph}")
        if move.countered_by:
            counters = ", ".join(move.countered_by)
            lines.append(f"  countered by: {counters}")
    else:
        # Show help text for non-combat commands
        if defn.help:
            lines.append(f"  help: {defn.help}")

    await player.send("\r\n".join(lines) + "\r\n")


async def _show_variant_overview(
    player: Player, defn: CommandDefinition, variants: list["CombatMove"]
) -> None:
    """Show overview of a variant command with all its variants.

    Args:
        player: The player requesting details
        defn: Command definition for the base command
        variants: List of variant moves
    """

    # Sort variants by name for consistent display
    variants = sorted(variants, key=lambda m: m.name)

    lines = [defn.name]

    # Check if any variant is locked
    if variants and variants[0].unlock_condition:
        base = variants[0].command or variants[0].name.split()[0]
        if base not in player.unlocked_moves:
            cond = variants[0].unlock_condition
            if cond.type == "kill_count":
                lock_msg = f"[LOCKED] Defeat {cond.threshold} enemies to unlock."
            elif cond.type == "mob_kills":
                mob = cond.mob_name
                lock_msg = f"[LOCKED] Defeat {cond.threshold} {mob}s to unlock."
            else:
                lock_msg = "[LOCKED]"
            lines.append(f"  {lock_msg}")

    # Show description from first variant (they all share the same one)
    if variants and variants[0].description:
        lines.append(f"  {variants[0].description}")

    # Show type from first variant
    if variants:
        lines.append(f"  type: {variants[0].move_type}")

    # Show each variant
    for move in variants:
        lines.append("")
        lines.append(f"  {move.name}")

        if move.aliases:
            aliases_str = ", ".join(move.aliases)
            lines.append(f"    aliases: {aliases_str}")

        lines.append(f"    stamina: {move.stamina_cost}")
        lines.append(f"    timing window: {move.timing_window_ms}ms")

        if move.damage_pct > 0:
            damage_pct = int(move.damage_pct * 100)
            lines.append(f"    damage: {damage_pct}%")

        if move.telegraph:
            lines.append(f"    telegraph: {move.telegraph}")

        if move.countered_by:
            counters = ", ".join(move.countered_by)
            lines.append(f"    countered by: {counters}")

    await player.send("\r\n".join(lines) + "\r\n")


async def cmd_commands(player: Player, args: str) -> None:
    """List all available commands grouped by type, or show detail for one.

    Args:
        player: The player executing the command
        args: Command arguments (command name for detail view, empty for list)
    """
    # If args provided, show detail view for that command
    if args.strip():
        await _show_command_detail(player, args.strip())
        return

    # Otherwise, show full list
    # Collect unique commands by CommandDefinition id (avoids alias duplication)
    seen: set[int] = set()
    unique_commands: list[CommandDefinition] = []

    for defn in _registry.values():
        defn_id = id(defn)
        if defn_id not in seen and not defn.hidden:
            seen.add(defn_id)
            unique_commands.append(defn)

    # Group commands by type (excluding combat moves)
    movement: list[CommandDefinition] = []
    other: list[CommandDefinition] = []

    for defn in unique_commands:
        # Check if it's a movement command
        if defn.name in DIRECTIONS:
            movement.append(defn)
        # Skip combat attacks and defenses
        elif defn.help.startswith("Attack with") or defn.help.startswith("Defend with"):
            continue
        # Everything else
        else:
            other.append(defn)

    all_commands = movement + other
    all_commands.sort(key=lambda d: d.name)
    names = [d.name for d in all_commands]

    await player.send("  ".join(names) + "\r\n")


async def cmd_skills(player: Player, args: str) -> None:
    """List all combat skills (attacks and defenses), or show detail for one.

    Args:
        player: The player executing the command
        args: Command arguments (skill name for detail view, empty for list)
    """
    # If args provided, show detail view for that skill
    if args.strip():
        await _show_command_detail(player, args.strip())
        return

    # Otherwise, show full list
    # Collect unique commands by CommandDefinition id (avoids alias duplication)
    seen: set[int] = set()
    unique_commands: list[CommandDefinition] = []

    for defn in _registry.values():
        defn_id = id(defn)
        if defn_id not in seen and not defn.hidden:
            seen.add(defn_id)
            unique_commands.append(defn)

    # Group combat moves
    attacks: list[CommandDefinition] = []
    defenses: list[CommandDefinition] = []

    for defn in unique_commands:
        # Check if it's a combat attack
        if defn.help.startswith("Attack with"):
            attacks.append(defn)
        # Check if it's a combat defense
        elif defn.help.startswith("Defend with"):
            defenses.append(defn)

    all_skills = attacks + defenses
    all_skills.sort(key=lambda d: d.name)
    names = [d.name for d in all_skills]

    await player.send("  ".join(names) + "\r\n")


async def cmd_client(player: Player, args: str) -> None:
    """Show client protocol negotiation and terminal capabilities."""
    lines = ["client protocols"]

    # Protocol status
    gmcp = "active" if player.gmcp_enabled else "not active"
    msdp = "active" if player.msdp_enabled else "not active"
    lines.append(f"  GMCP: {gmcp}")
    lines.append(f"  MSDP: {msdp}")

    # Terminal info
    lines.append("terminal")

    # Terminal type from TTYPE negotiation
    ttype = None
    if player.writer is not None:
        ttype = player.writer.get_extra_info("TERM") or None
    lines.append(f"  type: {ttype or 'unknown'}")

    # Terminal size from NAWS
    cols, rows = 80, 24
    if player.writer is not None:
        cols = player.writer.get_extra_info("cols") or 80
        rows = player.writer.get_extra_info("rows") or 24
    lines.append(f"  size: {cols}x{rows}")

    # Color depth
    lines.append(f"  colors: {player.color_depth}")

    # MTTS capabilities
    caps = player.caps
    mtts_flags = []
    if caps.ansi:
        mtts_flags.append("ANSI")
    if caps.vt100:
        mtts_flags.append("VT100")
    if caps.utf8:
        mtts_flags.append("UTF-8")
    if caps.colors_256:
        mtts_flags.append("256 colors")
    if caps.truecolor:
        mtts_flags.append("truecolor")
    if caps.mouse_tracking:
        mtts_flags.append("mouse tracking")
    if caps.screen_reader:
        mtts_flags.append("screen reader")
    if caps.proxy:
        mtts_flags.append("proxy")
    if caps.mnes:
        mtts_flags.append("MNES")
    if caps.mslp:
        mtts_flags.append("MSLP")
    if caps.ssl:
        mtts_flags.append("SSL")

    if mtts_flags:
        lines.append(f"  MTTS: {', '.join(mtts_flags)}")
    else:
        lines.append("  MTTS: none detected")

    await player.send("\r\n".join(lines) + "\r\n")


# Register the commands command
register(
    CommandDefinition(
        "commands",
        cmd_commands,
        aliases=["cmds"],
        mode="*",
        help="list available commands",
    )
)

# Register the skills command
register(
    CommandDefinition(
        "skills",
        cmd_skills,
        aliases=[],
        mode="*",
        help="list combat skills",
    )
)

# Register the client command
register(
    CommandDefinition(
        "client",
        cmd_client,
        aliases=[],
        mode="*",
        help="show client protocol and terminal info",
    )
)


async def cmd_help(player: Player, args: str) -> None:
    """Show help for a command or skill.

    Args:
        player: The player executing the command
        args: Command name to get help for
    """
    args = args.strip()
    if not args:
        await player.send(
            "type help <command> for details. see also: commands, skills, client\r\n"
        )
        return
    await _show_command_detail(player, args)


# Register the help command
register(
    CommandDefinition(
        "help",
        cmd_help,
        aliases=[],
        mode="*",
        help="show help for a command",
    )
)
