"""Player action models for the Mantimon TCG game engine.

This module defines all actions a player can take during their turn.
Actions are modeled as a discriminated union using Pydantic's Literal types,
enabling type-safe action handling and automatic JSON serialization.

The union type allows code like:
    match action:
        case PlayPokemonAction():
            handle_play_pokemon(action)
        case AttackAction():
            handle_attack(action)

Usage:
    # Create an action
    action = AttackAction(attack_index=0)

    # Serialize to JSON for WebSocket
    json_str = action.model_dump_json()

    # Parse incoming action from client
    data = {"type": "attack", "attack_index": 0}
    action = parse_action(data)  # Returns AttackAction
"""

from typing import Annotated, Any, Literal

from pydantic import BaseModel, Field


class PlayPokemonAction(BaseModel):
    """Play a Basic Pokemon from hand to the bench (or active during setup).

    This action places a Basic Pokemon card from the player's hand onto their
    bench. During the setup phase, it can optionally be placed as the active
    Pokemon.

    Attributes:
        type: Discriminator field, always "play_pokemon".
        card_instance_id: The CardInstance.instance_id of the Pokemon to play.
        to_active: If True and during setup, place as active Pokemon.
            Ignored during normal gameplay.
    """

    type: Literal["play_pokemon"] = "play_pokemon"
    card_instance_id: str
    to_active: bool = False


class EvolvePokemonAction(BaseModel):
    """Evolve a Pokemon in play.

    Places an evolution card from hand onto a Pokemon in play, provided
    evolution rules are satisfied (not same turn as played/evolved, etc.).

    Attributes:
        type: Discriminator field, always "evolve".
        evolution_card_id: The CardInstance.instance_id of the evolution card.
        target_pokemon_id: The CardInstance.instance_id of the Pokemon to evolve.
    """

    type: Literal["evolve"] = "evolve"
    evolution_card_id: str
    target_pokemon_id: str


class AttachEnergyAction(BaseModel):
    """Attach an energy card from hand (or energy zone) to a Pokemon.

    Energy can be attached to the active Pokemon or any benched Pokemon.
    Limited to once per turn by default (configurable via RulesConfig).

    In Pokemon Pocket style gameplay, energy is flipped from the energy_deck
    to the energy_zone at turn start. The player can then attach from the
    energy_zone rather than from hand.

    Attributes:
        type: Discriminator field, always "attach_energy".
        energy_card_id: The CardInstance.instance_id of the energy card.
        target_pokemon_id: The CardInstance.instance_id of the Pokemon to attach to.
        from_energy_zone: If True, the energy comes from the energy zone
            (Pokemon Pocket style) rather than hand.
    """

    type: Literal["attach_energy"] = "attach_energy"
    energy_card_id: str
    target_pokemon_id: str
    from_energy_zone: bool = False


class PlayTrainerAction(BaseModel):
    """Play a Trainer card from hand.

    The effect of the trainer card is resolved by the effect handler system.
    Some trainer cards require targets (specified in the targets list).

    Attributes:
        type: Discriminator field, always "play_trainer".
        card_instance_id: The CardInstance.instance_id of the trainer card.
        targets: List of target CardInstance IDs for cards that require targeting.
            The specific meaning depends on the card's effect.
        additional_params: Extra parameters for complex trainer effects.
    """

    type: Literal["play_trainer"] = "play_trainer"
    card_instance_id: str
    targets: list[str] = Field(default_factory=list)
    additional_params: dict[str, Any] = Field(default_factory=dict)


class UseAbilityAction(BaseModel):
    """Use an ability on a Pokemon in play.

    Abilities are typically limited to once per turn per Pokemon
    (configurable per ability).

    Attributes:
        type: Discriminator field, always "use_ability".
        pokemon_id: The CardInstance.instance_id of the Pokemon with the ability.
        ability_index: Index of the ability in the Pokemon's abilities list.
        targets: List of target CardInstance IDs if the ability requires targeting.
        additional_params: Extra parameters for complex ability effects.
    """

    type: Literal["use_ability"] = "use_ability"
    pokemon_id: str
    ability_index: int = 0
    targets: list[str] = Field(default_factory=list)
    additional_params: dict[str, Any] = Field(default_factory=dict)


class AttackAction(BaseModel):
    """Declare an attack with the active Pokemon.

    This action can only be performed during the attack phase and ends the turn.
    The attack's effect is resolved by the effect handler system.

    Attributes:
        type: Discriminator field, always "attack".
        attack_index: Index of the attack in the active Pokemon's attacks list.
        targets: List of target CardInstance IDs for attacks that allow targeting.
            Most attacks target the defending Pokemon automatically.
        additional_params: Extra parameters for complex attack effects.
    """

    type: Literal["attack"] = "attack"
    attack_index: int = 0
    targets: list[str] = Field(default_factory=list)
    additional_params: dict[str, Any] = Field(default_factory=dict)


class RetreatAction(BaseModel):
    """Retreat the active Pokemon, switching with a benched Pokemon.

    Requires discarding energy equal to the retreat cost (unless modified
    by effects). Limited to once per turn by default.

    Attributes:
        type: Discriminator field, always "retreat".
        new_active_id: The CardInstance.instance_id of the benched Pokemon
            to become the new active.
        energy_to_discard: List of CardInstance IDs of energy cards to discard
            as the retreat cost.
    """

    type: Literal["retreat"] = "retreat"
    new_active_id: str
    energy_to_discard: list[str] = Field(default_factory=list)


class PassAction(BaseModel):
    """Pass without taking an action, ending the current phase.

    During the main phase, this advances to the attack phase.
    During the attack phase, this ends the turn without attacking.

    Attributes:
        type: Discriminator field, always "pass".
    """

    type: Literal["pass"] = "pass"


class SelectPrizeAction(BaseModel):
    """Select a prize card to take after a knockout.

    Only used when using classic prize card rules (not the default point system).

    Attributes:
        type: Discriminator field, always "select_prize".
        prize_index: Index of the prize card to take (0-5 for 6 prizes).
    """

    type: Literal["select_prize"] = "select_prize"
    prize_index: int


class SelectActiveAction(BaseModel):
    """Select a new active Pokemon after the current active is knocked out.

    Attributes:
        type: Discriminator field, always "select_active".
        pokemon_id: The CardInstance.instance_id of the benched Pokemon
            to become the new active.
    """

    type: Literal["select_active"] = "select_active"
    pokemon_id: str


class ResignAction(BaseModel):
    """Resign from the match, conceding victory to the opponent.

    Attributes:
        type: Discriminator field, always "resign".
    """

    type: Literal["resign"] = "resign"


# Union type for all player actions
# Using Annotated with Field(discriminator) for automatic type resolution
Action = Annotated[
    PlayPokemonAction
    | EvolvePokemonAction
    | AttachEnergyAction
    | PlayTrainerAction
    | UseAbilityAction
    | AttackAction
    | RetreatAction
    | PassAction
    | SelectPrizeAction
    | SelectActiveAction
    | ResignAction,
    Field(discriminator="type"),
]


def parse_action(data: dict[str, Any]) -> Action:
    """Parse an action from a dictionary (e.g., from JSON).

    Uses the 'type' field to determine which action model to use.

    Args:
        data: Dictionary containing action data with a 'type' field.

    Returns:
        The appropriate Action subtype.

    Raises:
        ValueError: If the action type is unknown.
        ValidationError: If the data doesn't match the action schema.

    Example:
        data = {"type": "attack", "attack_index": 0}
        action = parse_action(data)
        assert isinstance(action, AttackAction)
    """
    from pydantic import TypeAdapter

    adapter = TypeAdapter(Action)
    return adapter.validate_python(data)


# Mapping of action types to their valid phases
# This is used by the rules validator to check if an action is valid
# in the current phase
VALID_PHASES_FOR_ACTION: dict[str, list[str]] = {
    "play_pokemon": ["setup", "main"],
    "evolve": ["main"],
    "attach_energy": ["main"],
    "play_trainer": ["main"],
    "use_ability": ["main"],
    "attack": ["attack"],
    "retreat": ["main"],
    "pass": ["main", "attack"],
    "select_prize": ["end"],
    "select_active": ["main", "end"],  # After knockout
    "resign": ["setup", "draw", "main", "attack", "end"],  # Any time
}
