"""Built-in effect handlers for Mantimon TCG.

This module provides the standard effect handlers used by most cards.
Each handler is registered using the @effect_handler decorator and can
be referenced by effect_id in card definitions.

Effect handlers follow a consistent pattern:
1. Extract parameters from context
2. Validate the action is possible
3. Apply the effect to game state
4. Return an EffectResult with details

Available Effects:
    Damage:
    - deal_damage: Raw damage (poison, burn, recoil, bench spread)
    - attack_damage: Combat damage with modifiers, weakness, resistance
    - coin_flip_damage: Damage based on coin flips
    - bench_damage: Damage to benched Pokemon

    Healing & Status:
    - heal: Heal damage from a Pokemon
    - apply_status: Apply a status condition
    - remove_status: Remove a status condition

    Card Movement:
    - draw_cards: Draw cards from deck to hand
    - discard_from_hand: Discard cards from hand
    - shuffle_deck: Shuffle a player's deck

    Energy & Modifiers:
    - discard_energy: Discard energy from a Pokemon
    - modify_hp: Change a Pokemon's HP modifier
    - modify_retreat_cost: Change a Pokemon's retreat cost modifier

    Evolution:
    - devolve: Remove evolution stages from a Pokemon
"""

from app.core.effects.base import EffectContext, EffectResult, EffectType
from app.core.effects.registry import effect_handler
from app.core.enums import ModifierMode, StatusCondition


@effect_handler("deal_damage")
def handle_deal_damage(ctx: EffectContext) -> EffectResult:
    """Deal raw damage to a target Pokemon.

    This is a primitive effect that applies damage directly without any
    combat modifiers, weakness, or resistance calculations. Use this for:
    - Poison/burn damage
    - Recoil damage
    - Ability damage that bypasses weakness/resistance
    - Bench spread damage

    For attack damage that should apply weakness/resistance, use attack_damage.

    Params:
        amount (int): Damage to deal. Required.

    Target:
        If target_card_id is set, damages that card.
        Otherwise, damages opponent's active Pokemon.

    Returns:
        Success with damage dealt, or failure if no valid target.
    """
    target = ctx.get_target_pokemon()
    if target is None:
        return EffectResult.failure("No valid target for damage")

    amount = ctx.get_int_param("amount", 0)
    if amount <= 0:
        return EffectResult.failure("Damage amount must be positive")

    target.damage += amount

    details: dict = {"amount": amount, "target_id": target.instance_id}

    # Check for knockout after applying damage
    card_def = ctx.game.get_card_definition(target.definition_id)
    if card_def and card_def.hp and target.is_knocked_out(card_def.hp):
        details["knockout"] = True
        details["knockout_pokemon_id"] = target.instance_id

    message = f"Dealt {amount} damage"
    if details.get("knockout"):
        message += f" - {target.definition_id} knocked out!"

    return EffectResult.success_result(
        message,
        effect_type=EffectType.DAMAGE,
        details=details,
    )


def _apply_modifier(damage: int, mode: ModifierMode, value: int) -> int:
    """Apply a damage modifier based on mode.

    Args:
        damage: The current damage amount.
        mode: Whether to multiply or add.
        value: The modifier value.

    Returns:
        The modified damage amount.
    """
    if mode == ModifierMode.MULTIPLICATIVE:
        return damage * value
    else:  # ADDITIVE
        return damage + value


@effect_handler("attack_damage")
def handle_attack_damage(ctx: EffectContext) -> EffectResult:
    """Deal attack damage to a target Pokemon with full combat calculations.

    Applies damage modifiers, weakness, and resistance in order:
    1. Start with base amount
    2. Add source Pokemon's damage_modifier (if include_modifiers=True)
    3. Apply weakness (mode and value from card or CombatConfig)
    4. Apply resistance (mode and value from card or CombatConfig)
    5. Apply final damage (minimum 0)

    Weakness/resistance calculation uses:
    - Card-specific mode/value if defined in WeaknessResistance
    - Game's CombatConfig defaults otherwise

    Params:
        amount (int): Base damage to deal. Required.
        include_modifiers (bool): Whether to add source's damage_modifier. Default True.
        apply_weakness_resistance (bool): Whether to apply weakness/resistance. Default True.

    Target:
        If target_card_id is set, damages that card.
        Otherwise, damages opponent's active Pokemon.

    Returns:
        Success with damage dealt and calculation breakdown, or failure if no valid target.
    """
    target = ctx.get_target_pokemon()
    if target is None:
        return EffectResult.failure("No valid target for damage")

    amount = ctx.get_int_param("amount", 0)
    if amount <= 0:
        return EffectResult.failure("Damage amount must be positive")

    details: dict = {
        "base_amount": amount,
        "target_id": target.instance_id,
    }

    # Step 1: Apply source Pokemon's damage modifier
    include_modifiers = ctx.get_param("include_modifiers", True)
    if include_modifiers:
        source = ctx.get_source_pokemon()
        if source and source.damage_modifier != 0:
            amount += source.damage_modifier
            details["damage_modifier"] = source.damage_modifier
            details["after_modifier"] = amount

    # Step 2: Apply weakness and resistance
    apply_wr = ctx.get_param("apply_weakness_resistance", True)
    target_def = ctx.get_card_definition(target)  # Needed for W/R and knockout check
    if apply_wr:
        source = ctx.get_source_pokemon()
        if source:
            # Get the attacker's type from its definition
            source_def = ctx.get_card_definition(source)
            combat_config = ctx.rules.combat

            if source_def and target_def and source_def.pokemon_type:
                attacker_type = source_def.pokemon_type

                # Check weakness
                if target_def.weakness and target_def.weakness.energy_type == attacker_type:
                    weakness = target_def.weakness
                    mode = weakness.get_mode(combat_config.weakness_mode)
                    value = weakness.get_value(combat_config.weakness_value)

                    amount = _apply_modifier(amount, mode, value)
                    details["weakness"] = {
                        "type": attacker_type.value,
                        "mode": mode.value,
                        "value": value,
                    }
                    details["after_weakness"] = amount

                # Check resistance
                if target_def.resistance and target_def.resistance.energy_type == attacker_type:
                    resistance = target_def.resistance
                    mode = resistance.get_mode(combat_config.resistance_mode)
                    value = resistance.get_value(combat_config.resistance_value)

                    amount = _apply_modifier(amount, mode, value)
                    details["resistance"] = {
                        "type": attacker_type.value,
                        "mode": mode.value,
                        "value": value,
                    }
                    details["after_resistance"] = amount

    # Final damage (minimum 0)
    actual_damage = max(0, amount)
    target.damage += actual_damage
    details["final_damage"] = actual_damage

    # Check for knockout after applying damage
    if target_def and target_def.hp and target.is_knocked_out(target_def.hp):
        details["knockout"] = True
        details["knockout_pokemon_id"] = target.instance_id

    message = f"Dealt {actual_damage} damage"
    if details.get("knockout"):
        message += f" - {target.definition_id} knocked out!"

    return EffectResult.success_result(
        message,
        effect_type=EffectType.DAMAGE,
        details=details,
    )


@effect_handler("heal")
def handle_heal(ctx: EffectContext) -> EffectResult:
    """Heal damage from a Pokemon.

    Params:
        amount (int): Amount of damage to heal. Required.

    Target:
        If target_card_id is set, heals that card.
        Otherwise, heals source player's active Pokemon.

    Returns:
        Success with amount healed, or failure if no valid target.
    """
    # Default to healing source player's active if no target specified
    target = ctx.get_target_card() if ctx.target_card_id else ctx.get_source_pokemon()

    if target is None:
        return EffectResult.failure("No valid target for healing")

    amount = ctx.get_int_param("amount", 0)
    if amount <= 0:
        return EffectResult.failure("Heal amount must be positive")

    # Don't heal more than current damage
    actual_heal = min(amount, target.damage)
    target.damage -= actual_heal

    return EffectResult.success_result(
        f"Healed {actual_heal} damage",
        effect_type=EffectType.HEAL,
        details={"amount": actual_heal, "target_id": target.instance_id},
    )


@effect_handler("draw_cards")
def handle_draw_cards(ctx: EffectContext) -> EffectResult:
    """Draw cards from deck to hand.

    Params:
        count (int): Number of cards to draw. Default 1.

    Draws cards for the source player.

    Returns:
        Success with number of cards drawn, or failure if deck is empty.
    """
    count = ctx.get_int_param("count", 1)
    if count <= 0:
        return EffectResult.failure("Draw count must be positive")

    player = ctx.get_source_player()
    cards_drawn = 0
    drawn_ids = []

    for _ in range(count):
        card = player.deck.draw()
        if card is None:
            break  # Deck empty
        player.hand.add(card)
        cards_drawn += 1
        drawn_ids.append(card.instance_id)

    if cards_drawn == 0:
        return EffectResult.failure("Deck is empty")

    message = f"Drew {cards_drawn} card{'s' if cards_drawn != 1 else ''}"
    return EffectResult.success_result(
        message,
        effect_type=EffectType.DRAW,
        details={"count": cards_drawn, "card_ids": drawn_ids},
    )


@effect_handler("discard_from_hand")
def handle_discard_from_hand(ctx: EffectContext) -> EffectResult:
    """Discard cards from hand to discard pile.

    Params:
        card_ids (list[str]): Instance IDs of cards to discard. Required.

    Discards cards from the source player's hand.

    Returns:
        Success with number discarded, or failure if cards not in hand.
    """
    card_ids = ctx.get_param("card_ids", [])
    if not card_ids:
        return EffectResult.failure("No cards specified to discard")

    player = ctx.get_source_player()
    discarded = []

    for card_id in card_ids:
        card = player.hand.remove(card_id)
        if card:
            player.discard.add(card)
            discarded.append(card_id)

    if not discarded:
        return EffectResult.failure("None of the specified cards were in hand")

    message = f"Discarded {len(discarded)} card{'s' if len(discarded) != 1 else ''}"
    return EffectResult.success_result(
        message,
        effect_type=EffectType.DISCARD,
        details={"count": len(discarded), "card_ids": discarded},
    )


@effect_handler("apply_status")
def handle_apply_status(ctx: EffectContext) -> EffectResult:
    """Apply a status condition to a Pokemon.

    Params:
        status (str): The StatusCondition value to apply. Required.
            One of: poisoned, burned, asleep, paralyzed, confused

    Target:
        If target_card_id is set, applies to that card.
        Otherwise, applies to opponent's active Pokemon.

    Returns:
        Success with status applied, or failure if invalid.
    """
    status_str = ctx.get_str_param("status")
    if not status_str:
        return EffectResult.failure("No status specified")

    try:
        status = StatusCondition(status_str)
    except ValueError:
        return EffectResult.failure(f"Invalid status condition: {status_str}")

    target = ctx.get_target_pokemon()
    if target is None:
        return EffectResult.failure("No valid target for status")

    target.add_status(status)

    return EffectResult.success_result(
        f"Applied {status.value}",
        effect_type=EffectType.STATUS,
        details={"status": status.value, "target_id": target.instance_id},
    )


@effect_handler("remove_status")
def handle_remove_status(ctx: EffectContext) -> EffectResult:
    """Remove a status condition from a Pokemon.

    Params:
        status (str): The StatusCondition value to remove. Required.
        all (bool): If True, remove all status conditions. Default False.

    Target:
        If target_card_id is set, removes from that card.
        Otherwise, removes from source player's active Pokemon.

    Returns:
        Success with status removed.
    """
    # Default to source player's active for self-healing effects
    target = ctx.get_target_card() if ctx.target_card_id else ctx.get_source_pokemon()

    if target is None:
        return EffectResult.failure("No valid target for status removal")

    remove_all = ctx.get_param("all", False)

    if remove_all:
        target.clear_all_status()
        return EffectResult.success_result(
            "Removed all status conditions",
            effect_type=EffectType.STATUS,
            details={"removed": "all", "target_id": target.instance_id},
        )

    status_str = ctx.get_str_param("status")
    if not status_str:
        return EffectResult.failure("No status specified")

    try:
        status = StatusCondition(status_str)
    except ValueError:
        return EffectResult.failure(f"Invalid status condition: {status_str}")

    had_status = target.has_status(status)
    target.remove_status(status)

    if had_status:
        return EffectResult.success_result(
            f"Removed {status.value}",
            effect_type=EffectType.STATUS,
            details={"status": status.value, "target_id": target.instance_id},
        )
    else:
        return EffectResult.success_result(
            f"Target did not have {status.value}",
            effect_type=EffectType.STATUS,
            details={"status": status.value, "target_id": target.instance_id, "had_status": False},
        )


# TODO: KNOCKOUT DETECTION FOR EFFECT-BASED DAMAGE
#
# The handlers below (coin_flip_damage, bench_damage) apply damage but do NOT
# currently detect knockouts. This is intentional for now because:
#
# 1. The engine doesn't yet execute attack effects - only base damage is applied
# 2. When effect execution is added, the engine should handle knockout detection
#    AFTER all effects resolve, not during each effect
# 3. The deal_damage/attack_damage handlers set knockout flags as informational
#    markers that the engine can use to identify which Pokemon to check
#
# When implementing effect execution in engine.py:
# - Execute all effects and collect EffectResults
# - Check details["knockout"] flag in each result
# - Call process_knockout() for each unique knockout_pokemon_id
# - Handle both active and bench knockouts (including multi-knockout scenarios)
#
# See: engine.py _execute_attack() for the TODO on integrating this


@effect_handler("attack_coin_status")
def handle_attack_coin_status(ctx: EffectContext) -> EffectResult:
    """Flip a coin; on heads, apply a status condition.

    This is the common "flip coin, apply status on heads" pattern used by many
    attacks like Thunder Shock (paralysis on heads).

    Params:
        status (str): StatusCondition to apply on heads. Required.
            One of: poisoned, burned, asleep, paralyzed, confused

    Target:
        Opponent's active Pokemon.

    Returns:
        Success with flip result and status application details.
    """
    status_str = ctx.get_str_param("status")
    if not status_str:
        return EffectResult.failure("No status specified")

    try:
        status = StatusCondition(status_str)
    except ValueError:
        return EffectResult.failure(f"Invalid status: {status_str}")

    flip = ctx.flip_coin()
    target = ctx.get_target_pokemon()

    if target is None:
        return EffectResult.failure("No valid target")

    if flip:
        target.add_status(status)
        return EffectResult.success_result(
            f"Flipped heads - applied {status.value}!",
            effect_type=EffectType.STATUS,
            details={"flip": "heads", "status": status.value, "target_id": target.instance_id},
        )
    else:
        return EffectResult.success_result(
            "Flipped tails - no effect",
            effect_type=EffectType.STATUS,
            details={"flip": "tails", "status": None},
        )


@effect_handler("coin_flip_damage")
def handle_coin_flip_damage(ctx: EffectContext) -> EffectResult:
    """Deal damage based on coin flip results.

    Note: This handler does NOT check for knockouts. The engine is responsible
    for knockout detection after all effects resolve. See module-level TODO.

    Params:
        damage_per_heads (int): Damage dealt per heads result. Required.
        flip_count (int): Number of coins to flip. Default 1.
        flip_until_tails (bool): If True, flip until tails and count heads. Default False.

    Target:
        Opponent's active Pokemon.

    Returns:
        Success with total damage and flip results.
    """
    damage_per_heads = ctx.get_int_param("damage_per_heads", 0)
    if damage_per_heads <= 0:
        return EffectResult.failure("damage_per_heads must be positive")

    flip_until_tails = ctx.get_param("flip_until_tails", False)

    if flip_until_tails:
        # Flip until tails
        heads_count = 0
        flips = []
        while True:
            flip = ctx.flip_coin()
            flips.append(flip)
            if flip:
                heads_count += 1
            else:
                break
    else:
        # Fixed number of flips
        flip_count = ctx.get_int_param("flip_count", 1)
        flips = ctx.flip_coins(flip_count)
        heads_count = sum(flips)

    total_damage = heads_count * damage_per_heads

    target = ctx.get_target_pokemon()
    if target is None:
        return EffectResult.failure("No valid target for damage")

    if total_damage > 0:
        target.damage += total_damage

    flip_results = ["heads" if f else "tails" for f in flips]
    message = f"Flipped {len(flips)} coin(s): {heads_count} heads. Dealt {total_damage} damage."

    return EffectResult.success_result(
        message,
        effect_type=EffectType.COIN_FLIP,
        details={
            "flips": flip_results,
            "heads_count": heads_count,
            "damage": total_damage,
            "target_id": target.instance_id,
        },
    )


@effect_handler("discard_energy")
def handle_discard_energy(ctx: EffectContext) -> EffectResult:
    """Discard energy from a Pokemon to its owner's discard pile.

    Params:
        count (int): Number of energy cards to discard. Default 1.
        energy_ids (list[str]): Specific energy instance IDs to discard. Optional.
            If not provided, discards from the end of attached_energy.

    Target:
        If target_card_id is set, discards from that Pokemon.
        Otherwise, discards from source player's active Pokemon.

    Note: Energy CardInstances are stored directly on the Pokemon. When discarded,
    they are moved to the Pokemon owner's discard pile.

    Returns:
        Success with energy discarded.
    """
    # Default to source player's active (for attack costs)
    target = ctx.get_target_card() if ctx.target_card_id else ctx.get_source_pokemon()

    if target is None:
        return EffectResult.failure("No valid target for energy discard")

    # Find the owner of the target Pokemon to access their discard pile
    owner = None
    for player in ctx.game.players.values():
        if target.instance_id in player.active or target.instance_id in player.bench:
            owner = player
            break

    if owner is None:
        return EffectResult.failure("Could not find owner of target Pokemon")

    energy_ids = ctx.get_param("energy_ids")
    count = ctx.get_int_param("count", 1)

    discarded: list[str] = []

    if energy_ids:
        # Discard specific energy by ID
        for energy_id in energy_ids:
            energy = target.detach_energy(energy_id)
            if energy:
                owner.discard.add(energy)
                discarded.append(energy_id)
    else:
        # Discard from end of list
        for _ in range(count):
            if target.attached_energy:
                energy = target.attached_energy.pop()
                owner.discard.add(energy)
                discarded.append(energy.instance_id)
            else:
                break

    if not discarded:
        return EffectResult.failure("No energy to discard")

    message = f"Discarded {len(discarded)} energy"
    return EffectResult.success_result(
        message,
        effect_type=EffectType.ENERGY,
        details={"count": len(discarded), "energy_ids": discarded, "target_id": target.instance_id},
    )


@effect_handler("devolve")
def handle_devolve(ctx: EffectContext) -> EffectResult:
    """Devolve a Pokemon by removing evolution stages.

    Removes evolution cards from a Pokemon, reverting it to a previous evolution
    stage. Energy, tools, damage, and status conditions remain on the devolved
    Pokemon. If damage exceeds the devolved Pokemon's HP, it is knocked out.

    Params:
        stages (int): Number of evolution stages to remove. Default 1.
            - 1: Remove most recent evolution (Stage 2 -> Stage 1, or Stage 1 -> Basic)
            - 2+: Remove multiple stages (Stage 2 -> Basic with stages=2)
        destination (str): Where removed evolution cards go. Default "hand".
            - "hand": Return removed evolutions to owner's hand
            - "discard": Send removed evolutions to owner's discard pile

    Target:
        The evolved Pokemon to devolve (via target_card_id). Must be an evolved
        Pokemon (has cards in cards_underneath).

    Returns:
        Success with details of removed cards, or failure if target cannot devolve.
        Includes knockout=True in details if devolve caused a knockout.
    """
    target = ctx.get_target_card()
    if target is None:
        return EffectResult.failure("No target specified for devolve")

    if not target.cards_underneath:
        return EffectResult.failure("Target is not an evolved Pokemon (no cards underneath)")

    # Find target's owner and zone
    owner = None
    zone = None
    for player in ctx.game.players.values():
        if target.instance_id in player.active:
            owner = player
            zone = player.active
            break
        elif target.instance_id in player.bench:
            owner = player
            zone = player.bench
            break

    if owner is None or zone is None:
        return EffectResult.failure("Could not find target Pokemon in play")

    stages = ctx.get_int_param("stages", 1)
    destination = ctx.get_str_param("destination", "hand")

    removed_cards: list[str] = []
    current = target

    for _ in range(stages):
        if not current.cards_underneath:
            break  # Can't devolve further

        # Get the previous evolution from the stack
        previous = current.cards_underneath.pop()

        # Transfer all state from current to previous
        # Energy, tools, and remaining stack stay with the Pokemon
        previous.attached_energy = current.attached_energy
        previous.attached_tools = current.attached_tools
        previous.cards_underneath = current.cards_underneath
        previous.damage = current.damage
        previous.status_conditions = current.status_conditions.copy()
        previous.hp_modifier = current.hp_modifier
        previous.damage_modifier = current.damage_modifier
        previous.retreat_cost_modifier = current.retreat_cost_modifier

        # Clear current's lists (they're now on previous)
        current.attached_energy = []
        current.attached_tools = []
        current.cards_underneath = []

        # Record the removed card
        removed_cards.append(current.instance_id)

        # Send removed evolution to destination
        if destination == "hand":
            owner.hand.add(current)
        else:
            owner.discard.add(current)

        # Swap in zone: remove current, add previous
        zone.remove(current.instance_id)
        zone.add(previous)

        # Previous becomes the new current for next iteration
        current = previous

    result_details: dict = {
        "removed_count": len(removed_cards),
        "removed_ids": removed_cards,
        "destination": destination,
        "devolved_pokemon_id": current.instance_id,
    }

    # Check for knockout after devolve (damage may exceed new lower HP)
    knockout_occurred = False
    if removed_cards:
        card_def = ctx.game.get_card_definition(current.definition_id)
        if card_def and card_def.hp and current.is_knocked_out(card_def.hp):
            knockout_occurred = True
            result_details["knockout"] = True
            result_details["knockout_pokemon_id"] = current.instance_id

    message = f"Devolved {len(removed_cards)} stage(s)"
    if knockout_occurred:
        message += f" - {current.definition_id} knocked out!"

    return EffectResult.success_result(
        message,
        effect_type=EffectType.SPECIAL,  # Devolve is a unique effect
        details=result_details,
    )


@effect_handler("modify_hp")
def handle_modify_hp(ctx: EffectContext) -> EffectResult:
    """Modify a Pokemon's HP modifier.

    Params:
        amount (int): Amount to add to hp_modifier. Can be negative.

    Target:
        If target_card_id is set, modifies that Pokemon.
        Otherwise, modifies source player's active Pokemon.

    Returns:
        Success with new modifier value.
    """
    target = ctx.get_target_card() if ctx.target_card_id else ctx.get_source_pokemon()

    if target is None:
        return EffectResult.failure("No valid target for HP modification")

    amount = ctx.get_int_param("amount", 0)
    target.hp_modifier += amount

    direction = "increased" if amount > 0 else "decreased"
    message = f"HP {direction} by {abs(amount)}"

    return EffectResult.success_result(
        message,
        effect_type=EffectType.MODIFIER,
        details={
            "amount": amount,
            "new_modifier": target.hp_modifier,
            "target_id": target.instance_id,
        },
    )


@effect_handler("modify_retreat_cost")
def handle_modify_retreat_cost(ctx: EffectContext) -> EffectResult:
    """Modify a Pokemon's retreat cost modifier.

    Params:
        amount (int): Amount to add to retreat_cost_modifier. Can be negative.

    Target:
        If target_card_id is set, modifies that Pokemon.
        Otherwise, modifies source player's active Pokemon.

    Returns:
        Success with new modifier value.
    """
    target = ctx.get_target_card() if ctx.target_card_id else ctx.get_source_pokemon()

    if target is None:
        return EffectResult.failure("No valid target for retreat cost modification")

    amount = ctx.get_int_param("amount", 0)
    target.retreat_cost_modifier += amount

    direction = "increased" if amount > 0 else "decreased"
    message = f"Retreat cost {direction} by {abs(amount)}"

    return EffectResult.success_result(
        message,
        effect_type=EffectType.MODIFIER,
        details={
            "amount": amount,
            "new_modifier": target.retreat_cost_modifier,
            "target_id": target.instance_id,
        },
    )


@effect_handler("shuffle_deck")
def handle_shuffle_deck(ctx: EffectContext) -> EffectResult:
    """Shuffle a player's deck.

    Params:
        target_opponent (bool): If True, shuffle opponent's deck. Default False.

    Returns:
        Success after shuffling.
    """
    target_opponent = ctx.get_param("target_opponent", False)
    player = ctx.get_opponent() if target_opponent else ctx.get_source_player()

    player.deck.shuffle(ctx.rng)

    return EffectResult.success_result(
        "Shuffled deck",
        effect_type=EffectType.SHUFFLE,
        details={"player_id": player.player_id},
    )


@effect_handler("bench_damage")
def handle_bench_damage(ctx: EffectContext) -> EffectResult:
    """Deal damage to benched Pokemon.

    Note: This handler does NOT check for knockouts. The engine is responsible
    for knockout detection after all effects resolve. See module-level TODO.

    Params:
        amount (int): Damage to deal to each target. Required.
        target_opponent (bool): Target opponent's bench. Default True.
        target_all (bool): Target all benched Pokemon. Default True.
        target_count (int): Number of benched Pokemon to target (if not all).

    Returns:
        Success with total damage dealt.
    """
    amount = ctx.get_int_param("amount", 0)
    if amount <= 0:
        return EffectResult.failure("Damage amount must be positive")

    target_opponent = ctx.get_param("target_opponent", True)
    target_all = ctx.get_param("target_all", True)
    target_count = ctx.get_int_param("target_count", 1)

    player = ctx.get_opponent() if target_opponent else ctx.get_source_player()
    benched = player.bench.get_all()

    if not benched:
        return EffectResult.success_result(
            "No benched Pokemon to damage",
            effect_type=EffectType.BENCH,
            details={"targets_hit": 0},
        )

    targets = benched if target_all else benched[:target_count]

    total_damage = 0
    target_ids = []

    for pokemon in targets:
        pokemon.damage += amount
        total_damage += amount
        target_ids.append(pokemon.instance_id)

    message = f"Dealt {amount} damage to {len(targets)} benched Pokemon"
    return EffectResult.success_result(
        message,
        effect_type=EffectType.BENCH,
        details={
            "amount_per_target": amount,
            "total_damage": total_damage,
            "target_ids": target_ids,
        },
    )
