//! Shared Data Transfer Objects for serializing game state.
//!
//! These DTOs define the JSON wire format used between the MCP server
//! and the Tauri UI. Both crates import from here to stay in sync.

use crate::cards::{
    AbilityDefinition, CardDefinition, CommanderDefinition, EffectDefinition, PassiveEffectDefinition,
    PassiveModifier,
};
use crate::client_api::GameEvent;
use crate::core::effects::{TargetingRule, TokenAbility, TokenEffect, Trigger};
use crate::core::state::GameResult;
use crate::types::CardId;
use crate::core::cards::Faction as CardFaction;
use crate::{Action, CardDatabase, CardType, Creature, Faction, Keywords, Support, Target};
use serde::{Deserialize, Serialize};

// =============================================================================
// DTO Structs
// =============================================================================

/// Full game state sent to the frontend.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GameStateDto {
    pub id: String,
    pub turn: u16,
    pub phase: String,
    pub active_player: u8,

    pub player: PlayerStateDto,
    pub opponent: PlayerStateDto,

    pub is_game_over: bool,
    pub winner: Option<u8>,
    pub game_over_reason: Option<String>,
}

/// Player state (our side vs opponent side).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlayerStateDto {
    pub life: i16,
    pub max_life: i16,
    pub essence: u8,
    pub max_essence: u8,
    pub action_points: u8,
    pub deck_count: usize,
    /// Essence extracted from opponent (total face damage dealt).
    /// In EssenceWar mode, reaching 50 wins the game.
    pub essence_extracted: u16,

    pub hand: Vec<CardDto>,
    pub creatures: Vec<Option<CreatureDto>>,
    pub supports: Vec<Option<SupportDto>>,

    /// Commander information (always present in a game).
    pub commander: Option<CommanderDto>,
}

/// Commander information for display.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommanderDto {
    pub id: u16,
    pub name: String,
    pub faction: String,
    pub ability_description: String,
    /// Portrait path (relative to static folder).
    pub portrait_path: String,
}

/// Card in hand.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CardDto {
    pub card_id: u16,
    pub name: String,
    pub cost: u8,
    pub card_type: String,
    pub faction: String,

    // Creature stats (if applicable)
    pub attack: Option<u8>,
    pub health: Option<u8>,
    pub keywords: Vec<String>,

    // Support stats (if applicable)
    pub durability: Option<u8>,

    // Art path (relative to assets)
    pub art_path: Option<String>,

    /// Effect description for spell/support cards (None for creatures).
    pub effect_description: Option<String>,
}

/// Activated ability on a creature (typically tokens).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AbilityDto {
    /// Ability index (0-5) for action matching.
    pub index: u8,
    /// Display name (e.g., "Fungal Rot").
    pub name: String,
    /// Essence cost to activate.
    pub essence_cost: u8,
    /// Targeting type: "no_target", "enemy_creature", "enemy_player", "any", etc.
    pub targeting_type: String,
    /// Human-readable effect description.
    pub description: String,
}

/// Creature on board.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatureDto {
    pub instance_id: u32,
    pub card_id: u16,
    pub name: String,
    pub slot: u8,
    pub faction: String,

    pub attack: i8,
    pub base_attack: u8,
    pub health: i8,
    pub max_health: i8,

    pub keywords: Vec<String>,
    pub can_attack: bool,
    pub is_exhausted: bool,

    pub art_path: Option<String>,

    /// Activated abilities (empty for most creatures, populated for tokens with abilities).
    pub abilities: Vec<AbilityDto>,
}

/// Support on board.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SupportDto {
    pub card_id: u16,
    pub name: String,
    pub slot: u8,
    pub faction: String,

    pub durability: u8,

    pub art_path: Option<String>,

    /// Human-readable description of what this support does.
    pub effect_description: String,
}

/// Action information for the UI.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ActionInfo {
    pub index: u16,
    pub action_type: String,
    pub description: String,

    // For highlighting
    pub source_slot: Option<u8>,
    pub target_slot: Option<u8>,
    pub hand_index: Option<u8>,
    pub card_id: Option<u16>,

    /// For use_ability actions: which ability (0-5).
    pub ability_index: Option<u8>,
}

/// Game state update after an action.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GameStateUpdate {
    pub state: GameStateDto,
    pub last_action: Option<ActionInfo>,
    pub events: Vec<GameEventDto>,
}

/// Game event for animation/sound triggers.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GameEventDto {
    pub event_type: String,
    pub data: serde_json::Value,
}

/// Result of a completed game.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GameResultDto {
    pub winner: Option<u8>,
    pub reason: String,
    pub final_turn: u16,
    pub player_final_life: i16,
    pub opponent_final_life: i16,
}

// =============================================================================
// Utility Functions
// =============================================================================

/// Derive faction from card ID (based on ID ranges).
pub fn faction_from_card_id(card_id: u16) -> &'static str {
    match card_id {
        1000..=1999 => "argentum",
        2000..=2999 => "symbiote",
        3000..=3999 => "obsidion",
        4000..=4999 => "neutral",
        _ => "unknown",
    }
}

/// Convert a deck Faction enum to its string representation.
pub fn faction_to_string(faction: Faction) -> &'static str {
    match faction {
        Faction::Argentum => "argentum",
        Faction::Symbiote => "symbiote",
        Faction::Obsidion => "obsidion",
        Faction::Neutral => "neutral",
    }
}

/// Convert a card Faction enum to its string representation.
pub fn card_faction_to_string(faction: CardFaction) -> &'static str {
    match faction {
        CardFaction::Argentum => "argentum",
        CardFaction::Symbiote => "symbiote",
        CardFaction::Obsidion => "obsidion",
        CardFaction::Neutral => "neutral",
    }
}

/// Convert a card type to its string representation.
pub fn card_type_to_string(card_type: &CardType) -> &'static str {
    match card_type {
        CardType::Creature { .. } => "creature",
        CardType::Spell { .. } => "spell",
        CardType::Support { .. } => "support",
    }
}

/// Convert keywords bitmask to a list of keyword strings.
pub fn keywords_to_strings(keywords: &Keywords) -> Vec<String> {
    let mut result = Vec::new();
    if keywords.has_rush() { result.push("Rush".to_string()); }
    if keywords.has_guard() { result.push("Guard".to_string()); }
    if keywords.has_ranged() { result.push("Ranged".to_string()); }
    if keywords.has_piercing() { result.push("Piercing".to_string()); }
    if keywords.has_lifesteal() { result.push("Lifesteal".to_string()); }
    if keywords.has_lethal() { result.push("Lethal".to_string()); }
    if keywords.has_shield() { result.push("Shield".to_string()); }
    if keywords.has_quick() { result.push("Quick".to_string()); }
    if keywords.has_ephemeral() { result.push("Ephemeral".to_string()); }
    if keywords.has_regenerate() { result.push("Regenerate".to_string()); }
    if keywords.has_stealth() { result.push("Stealth".to_string()); }
    if keywords.has_charge() { result.push("Charge".to_string()); }
    if keywords.has_frenzy() { result.push("Frenzy".to_string()); }
    if keywords.has_volatile() { result.push("Volatile".to_string()); }
    result
}

/// Convert a commander ID to a CommanderDto, looking up the definition in the database.
pub fn commander_to_dto(commander_id: Option<CardId>, card_db: &CardDatabase) -> Option<CommanderDto> {
    let id = commander_id?;
    let commander = card_db.get_commander(id)?;
    Some(CommanderDto::from_commander_def(commander))
}

/// Convert a TargetingRule to a frontend-friendly string.
pub fn targeting_rule_to_string(rule: &TargetingRule) -> &'static str {
    match rule {
        TargetingRule::NoTarget => "no_target",
        TargetingRule::TargetCreature(_) => "creature",
        TargetingRule::TargetAllyCreature => "ally_creature",
        TargetingRule::TargetEnemyCreature => "enemy_creature",
        TargetingRule::TargetPlayer => "player",
        TargetingRule::TargetEnemyPlayer => "enemy_player",
        TargetingRule::TargetAny => "any",
        TargetingRule::TargetSlot => "slot",
    }
}

// =============================================================================
// Effect Description Generators
// =============================================================================

/// Generate a human-readable description of token effects.
fn describe_token_effects(effects: &[TokenEffect]) -> String {
    let parts: Vec<String> = effects
        .iter()
        .filter_map(|effect| match effect {
            TokenEffect::DestroySelf => None,
            TokenEffect::Damage { amount } => Some(format!("{} damage", amount)),
            TokenEffect::Debuff { attack, health } => {
                if *attack != 0 && *health != 0 {
                    Some(format!("{:+}/{:+}", attack, health))
                } else if *attack != 0 {
                    Some(format!("{:+} attack", attack))
                } else {
                    Some(format!("{:+} health", health))
                }
            }
            TokenEffect::HealSelf { amount } => Some(format!("heal {} to self", amount)),
        })
        .collect();

    if parts.is_empty() {
        "Activate".to_string()
    } else {
        parts.join(", ")
    }
}

/// Convert token abilities to AbilityDto list.
fn token_abilities_to_dtos(abilities: Option<&[TokenAbility]>) -> Vec<AbilityDto> {
    abilities
        .map(|abs| {
            abs.iter()
                .enumerate()
                .map(|(i, ability)| AbilityDto {
                    index: i as u8,
                    name: ability.name.clone(),
                    essence_cost: ability.essence_cost,
                    targeting_type: targeting_rule_to_string(&ability.targeting).to_string(),
                    description: describe_token_effects(&ability.effects),
                })
                .collect()
        })
        .unwrap_or_default()
}

/// Describe a trigger in human-readable form.
fn trigger_to_string(trigger: &Trigger) -> &'static str {
    match trigger {
        Trigger::OnPlay => "On play",
        Trigger::OnAttack => "On attack",
        Trigger::OnDealDamage => "On deal damage",
        Trigger::OnTakeDamage => "On take damage",
        Trigger::OnKill => "On kill",
        Trigger::OnDeath => "On death",
        Trigger::StartOfTurn => "Start of turn",
        Trigger::EndOfTurn => "End of turn",
        Trigger::OnAllyPlayed => "On ally played",
        Trigger::OnAllyDeath => "On ally death",
        Trigger::OnEnemyDeath => "On enemy death",
        Trigger::OnCreaturePlayed => "On creature played",
        Trigger::Activated => "Activated",
    }
}

/// Describe an effect in human-readable form.
fn describe_effect(effect: &EffectDefinition) -> String {
    match effect {
        EffectDefinition::Damage { amount, filter } => {
            let target = filter.as_ref().map_or("target", |_| "filtered targets");
            format!("Deal {} damage to {}", amount, target)
        }
        EffectDefinition::Heal { amount, filter } => {
            let target = filter.as_ref().map_or("your commander", |_| "filtered creatures");
            format!("Heal {} to {}", amount, target)
        }
        EffectDefinition::Draw { count } => {
            if *count == 1 {
                "Draw a card".to_string()
            } else {
                format!("Draw {} cards", count)
            }
        }
        EffectDefinition::BuffStats { attack, health, filter } => {
            let target = filter.as_ref().map_or("target", |_| "filtered creatures");
            format!("Give {} {:+}/{:+}", target, attack, health)
        }
        EffectDefinition::Destroy { .. } => "Destroy target".to_string(),
        EffectDefinition::GrantKeyword { keyword, .. } => {
            format!("Grant {}", keyword)
        }
        EffectDefinition::RemoveKeyword { keyword, .. } => {
            format!("Remove {}", keyword)
        }
        EffectDefinition::Silence { .. } => "Silence target".to_string(),
        EffectDefinition::GainEssence { amount } => {
            format!("Gain {} essence", amount)
        }
        EffectDefinition::RefreshCreature => "Refresh creature".to_string(),
        EffectDefinition::Bounce { .. } => "Return to hand".to_string(),
        EffectDefinition::SummonToken { token } => {
            format!("Summon a {}/{} {}", token.attack, token.health, token.name)
        }
        EffectDefinition::Transform { into } => {
            format!("Transform into {}/{} {}", into.attack, into.health, into.name)
        }
        EffectDefinition::Copy => "Create a copy".to_string(),
    }
}

/// Describe a passive modifier.
fn describe_passive(passive: &PassiveEffectDefinition) -> String {
    match &passive.modifier {
        PassiveModifier::AttackBonus(amount) => {
            format!("Your creatures have {:+} Attack", amount)
        }
        PassiveModifier::HealthBonus(amount) => {
            format!("Your creatures have {:+} Health", amount)
        }
        PassiveModifier::GrantKeyword(keyword) => {
            format!("Your creatures have {}", keyword)
        }
    }
}

/// Describe a triggered ability.
fn describe_triggered_ability(ability: &AbilityDefinition) -> String {
    let trigger = trigger_to_string(&ability.trigger);
    let effects: Vec<String> = ability.effects.iter().map(describe_effect).collect();
    format!("{}: {}", trigger, effects.join(", "))
}

/// Generate a human-readable description of what a spell does.
pub fn describe_spell_effects(
    targeting: &TargetingRule,
    effects: &[EffectDefinition],
) -> String {
    if effects.is_empty() {
        return "No effect".to_string();
    }

    let effect_descriptions: Vec<String> = effects.iter().map(describe_effect).collect();
    let effects_text = effect_descriptions.join(". ");

    match targeting {
        TargetingRule::NoTarget => effects_text,
        TargetingRule::TargetCreature(_) => effects_text,
        TargetingRule::TargetAllyCreature => effects_text,
        TargetingRule::TargetEnemyCreature => effects_text,
        TargetingRule::TargetPlayer => effects_text,
        TargetingRule::TargetEnemyPlayer => effects_text,
        TargetingRule::TargetAny => effects_text,
        TargetingRule::TargetSlot => effects_text,
    }
}

/// Generate a human-readable description of what a support does.
pub fn describe_support_effects(
    passives: &[PassiveEffectDefinition],
    triggered: &[AbilityDefinition],
) -> String {
    let mut parts = Vec::new();

    for passive in passives {
        parts.push(describe_passive(passive));
    }

    for ability in triggered {
        parts.push(describe_triggered_ability(ability));
    }

    if parts.is_empty() {
        "No effects".to_string()
    } else {
        parts.join(". ")
    }
}

/// Infer faction from creature keywords for tokens.
fn infer_faction_from_keywords(keywords: &Keywords) -> String {
    // Argentum: Guard, Piercing, Shield
    if keywords.has_guard() || keywords.has_piercing() || keywords.has_shield() {
        return "argentum".to_string();
    }
    // Symbiote: Rush, Lethal, Regenerate
    if keywords.has_rush() || keywords.has_lethal() || keywords.has_regenerate() {
        return "symbiote".to_string();
    }
    // Obsidion: Lifesteal, Stealth, Quick
    if keywords.has_lifesteal() || keywords.has_stealth() || keywords.has_quick() {
        return "obsidion".to_string();
    }
    "neutral".to_string()
}

/// Generate token art path (snake_case name).
fn token_art_path(name: &str) -> String {
    let snake_name = name.to_lowercase().replace(' ', "_");
    format!("tokens/named/{}.webp", snake_name)
}

// =============================================================================
// Builder Implementations
// =============================================================================

impl CommanderDto {
    /// Create a CommanderDto from a CommanderDefinition.
    pub fn from_commander_def(commander: &CommanderDefinition) -> Self {
        let faction = card_faction_to_string(commander.faction);
        let ability_description = commander.ability_description();
        let portrait_name = commander.name.to_lowercase().replace(' ', "_");
        let portrait_path = format!("portrait/{}.webp", portrait_name);

        Self {
            id: commander.id,
            name: commander.name.clone(),
            faction: faction.to_string(),
            ability_description,
            portrait_path,
        }
    }
}

impl CardDto {
    /// Create a CardDto from a CardDefinition.
    pub fn from_card_def(card: &CardDefinition) -> Self {
        let faction = faction_from_card_id(card.id);
        let card_type_str = card_type_to_string(&card.card_type);

        let keywords: Vec<String> = match &card.card_type {
            CardType::Creature { keywords, .. } => keywords.clone(),
            _ => Vec::new(),
        };

        let art_path = Some(format!("cards/core_set/{}.webp", card.id));

        let effect_description = match &card.card_type {
            CardType::Spell { targeting, effects, .. } => {
                Some(describe_spell_effects(targeting, effects))
            }
            CardType::Support { passive_effects, triggered_effects, .. } => {
                Some(describe_support_effects(passive_effects, triggered_effects))
            }
            _ => None,
        };

        Self {
            card_id: card.id,
            name: card.name.clone(),
            cost: card.cost,
            card_type: card_type_str.to_string(),
            faction: faction.to_string(),
            attack: card.attack(),
            health: card.health(),
            keywords,
            durability: card.durability(),
            art_path,
            effect_description,
        }
    }

    /// Create a hidden card DTO (for opponent's hand).
    pub fn hidden() -> Self {
        Self {
            card_id: 0,
            name: "Hidden".to_string(),
            cost: 0,
            card_type: "unknown".to_string(),
            faction: "unknown".to_string(),
            attack: None,
            health: None,
            keywords: Vec::new(),
            durability: None,
            art_path: None,
            effect_description: None,
        }
    }
}

impl CreatureDto {
    /// Create a DTO from a creature with its card definition.
    pub fn from_creature(creature: &Creature, card: &CardDefinition, current_turn: u16) -> Self {
        let faction = faction_from_card_id(card.id);
        let keywords = keywords_to_strings(&creature.keywords);
        let art_path = Some(format!("cards/core_set/{}.webp", card.id));
        let abilities = token_abilities_to_dtos(creature.token_abilities());

        Self {
            instance_id: creature.instance_id.0,
            card_id: card.id,
            name: card.name.clone(),
            slot: creature.slot.0,
            faction: faction.to_string(),
            attack: creature.attack,
            base_attack: creature.base_attack,
            health: creature.current_health,
            max_health: creature.max_health,
            keywords,
            can_attack: creature.can_attack(current_turn),
            is_exhausted: creature.status.is_exhausted(),
            art_path,
            abilities,
        }
    }

    /// Create a DTO for a token creature (card_id 0).
    /// Tokens don't have card database entries, so we use the creature's stats directly.
    pub fn from_token(creature: &Creature, current_turn: u16) -> Self {
        let keywords = keywords_to_strings(&creature.keywords);
        let abilities = token_abilities_to_dtos(creature.token_abilities());

        let name = creature
            .token_name()
            .map(|s| s.to_string())
            .unwrap_or_else(|| "Token".to_string());

        let faction = infer_faction_from_keywords(&creature.keywords);
        let art_path = Some(token_art_path(&name));

        Self {
            instance_id: creature.instance_id.0,
            card_id: 0,
            name,
            slot: creature.slot.0,
            faction,
            attack: creature.attack,
            base_attack: creature.base_attack,
            health: creature.current_health,
            max_health: creature.max_health,
            keywords,
            can_attack: creature.can_attack(current_turn),
            is_exhausted: creature.status.is_exhausted(),
            art_path,
            abilities,
        }
    }
}

impl SupportDto {
    /// Create a DTO from a support with its card definition.
    pub fn from_support(support: &Support, card: &CardDefinition) -> Self {
        let faction = faction_from_card_id(card.id);
        let art_path = Some(format!("cards/core_set/{}.webp", card.id));

        let effect_description = match &card.card_type {
            CardType::Support { passive_effects, triggered_effects, .. } => {
                describe_support_effects(passive_effects, triggered_effects)
            }
            _ => "Unknown support".to_string(),
        };

        Self {
            card_id: card.id,
            name: card.name.clone(),
            slot: support.slot.0,
            faction: faction.to_string(),
            durability: support.current_durability,
            art_path,
            effect_description,
        }
    }
}

// =============================================================================
// Action & Event Conversion
// =============================================================================

/// Convert a game action to an ActionInfo DTO.
pub fn action_to_info(action: &Action, index: u16) -> ActionInfo {
    match action {
        Action::PlayCard { hand_index, slot } => ActionInfo {
            index,
            action_type: "play_card".to_string(),
            description: format!("Play card from hand {} to slot {}", hand_index, slot.0),
            source_slot: None,
            target_slot: Some(slot.0),
            hand_index: Some(*hand_index),
            card_id: None,
            ability_index: None,
        },
        Action::Attack { attacker, defender } => ActionInfo {
            index,
            action_type: "attack".to_string(),
            description: format!("Attack slot {} with creature in slot {}", defender.0, attacker.0),
            source_slot: Some(attacker.0),
            target_slot: Some(defender.0),
            hand_index: None,
            card_id: None,
            ability_index: None,
        },
        Action::UseAbility { slot, ability_index, target } => {
            let (target_desc, target_slot) = match target {
                Target::NoTarget => ("".to_string(), None),
                Target::EnemySlot(s) => (format!(" on enemy slot {}", s.0), Some(s.0)),
                Target::AllySlot(s) => (format!(" on ally slot {}", s.0), Some(s.0)),
            };
            ActionInfo {
                index,
                action_type: "use_ability".to_string(),
                description: format!("Use ability {} from slot {}{}", ability_index, slot.0, target_desc),
                source_slot: Some(slot.0),
                target_slot,
                hand_index: None,
                card_id: None,
                ability_index: Some(*ability_index),
            }
        }
        Action::CommanderInsight => ActionInfo {
            index,
            action_type: "commander_insight".to_string(),
            description: "Commander's Insight - draw a card for 4 essence".to_string(),
            source_slot: None,
            target_slot: None,
            hand_index: None,
            card_id: None,
            ability_index: None,
        },
        Action::EndTurn => ActionInfo {
            index,
            action_type: "end_turn".to_string(),
            description: "End turn".to_string(),
            source_slot: None,
            target_slot: None,
            hand_index: None,
            card_id: None,
            ability_index: None,
        },
    }
}

/// Convert a GameEvent to a GameEventDto.
pub fn game_event_to_dto(event: &GameEvent) -> GameEventDto {
    let (event_type, data) = match event {
        GameEvent::GameStarted { seed, mode, .. } => (
            "game_started",
            serde_json::json!({ "seed": seed, "mode": format!("{:?}", mode) }),
        ),
        GameEvent::TurnStarted { player, turn_number, .. } => (
            "turn_started",
            serde_json::json!({ "player": player.0 + 1, "turn": turn_number }),
        ),
        GameEvent::TurnEnded { player, turn_number } => (
            "turn_ended",
            serde_json::json!({ "player": player.0 + 1, "turn": turn_number }),
        ),
        GameEvent::ActionTaken { player, action, turn } => (
            "action_taken",
            serde_json::json!({
                "player": player.0 + 1,
                "action": format!("{:?}", action),
                "turn": turn
            }),
        ),
        GameEvent::CreatureSpawned { player, slot, card_id, .. } => (
            "creature_spawned",
            serde_json::json!({ "player": player.0 + 1, "slot": slot.0, "card_id": card_id.0 }),
        ),
        GameEvent::CreatureDied { player, slot, card_id, .. } => (
            "creature_died",
            serde_json::json!({ "player": player.0 + 1, "slot": slot.0, "card_id": card_id.0 }),
        ),
        GameEvent::LifeChanged { player, old_life, new_life, source } => (
            "life_changed",
            serde_json::json!({
                "player": player.0 + 1,
                "old": old_life,
                "new": new_life,
                "source": format!("{:?}", source)
            }),
        ),
        GameEvent::KeywordActivated { player, slot, keyword, value, target_player, target_slot } => (
            "keyword_activated",
            serde_json::json!({
                "player": player.0 + 1,
                "slot": slot.0,
                "keyword": format!("{:?}", keyword),
                "value": value,
                "target_player": target_player.map(|p| p.0 + 1),
                "target_slot": target_slot.map(|s| s.0)
            }),
        ),
        GameEvent::GameEnded { result, final_turn } => {
            let (winner, reason) = match result {
                GameResult::Win { winner, reason } => (Some(winner.0 + 1), format!("{:?}", reason)),
                GameResult::Draw => (None, "draw".to_string()),
            };
            (
                "game_ended",
                serde_json::json!({
                    "winner": winner,
                    "reason": reason,
                    "final_turn": final_turn
                }),
            )
        }
        GameEvent::CardDrawn { player, card_id, hand_position, cards_remaining_in_deck } => (
            "card_drawn",
            serde_json::json!({
                "player": player.0 + 1,
                "cardId": card_id.0,
                "handPosition": hand_position,
                "deckRemaining": cards_remaining_in_deck
            }),
        ),
        GameEvent::CardPlayed { player, card_id, hand_index, target_slot, essence_cost } => (
            "card_played",
            serde_json::json!({
                "player": player.0 + 1,
                "cardId": card_id.0,
                "handIndex": hand_index,
                "targetSlot": target_slot.0,
                "essenceCost": essence_cost
            }),
        ),
        GameEvent::CardDiscarded { player, card_id, reason } => (
            "card_discarded",
            serde_json::json!({
                "player": player.0 + 1,
                "cardId": card_id.0,
                "reason": format!("{:?}", reason)
            }),
        ),
        GameEvent::CreatureDamaged { player, slot, damage, new_health, source } => (
            "creature_damaged",
            serde_json::json!({
                "player": player.0 + 1,
                "slot": slot.0,
                "damage": damage,
                "newHealth": new_health,
                "source": format!("{:?}", source)
            }),
        ),
        GameEvent::CreatureHealed { player, slot, amount, new_health } => (
            "creature_healed",
            serde_json::json!({
                "player": player.0 + 1,
                "slot": slot.0,
                "amount": amount,
                "newHealth": new_health
            }),
        ),
        GameEvent::CreatureStatsChanged { player, slot, old_attack, new_attack, old_health, new_health } => (
            "creature_stats_changed",
            serde_json::json!({
                "player": player.0 + 1,
                "slot": slot.0,
                "oldAttack": old_attack,
                "newAttack": new_attack,
                "oldHealth": old_health,
                "newHealth": new_health
            }),
        ),
        GameEvent::CreatureKeywordsChanged { player, slot, old_keywords, new_keywords } => (
            "creature_keywords_changed",
            serde_json::json!({
                "player": player.0 + 1,
                "slot": slot.0,
                "oldKeywords": format!("{:?}", old_keywords),
                "newKeywords": format!("{:?}", new_keywords)
            }),
        ),
        GameEvent::CreatureBounced { player, slot, card_id } => (
            "creature_bounced",
            serde_json::json!({
                "player": player.0 + 1,
                "slot": slot.0,
                "cardId": card_id.0
            }),
        ),
        GameEvent::CombatStarted { attacker_player, attacker_slot, defender_player, defender_slot } => (
            "combat_started",
            serde_json::json!({
                "attackerPlayer": attacker_player.0 + 1,
                "attackerSlot": attacker_slot.0,
                "defenderPlayer": defender_player.0 + 1,
                "defenderSlot": defender_slot.0
            }),
        ),
        GameEvent::CombatResolved { attacker_damage_dealt, defender_damage_dealt, attacker_died, defender_died } => (
            "combat_resolved",
            serde_json::json!({
                "attackerDamageDealt": attacker_damage_dealt,
                "defenderDamageDealt": defender_damage_dealt,
                "attackerDied": attacker_died,
                "defenderDied": defender_died
            }),
        ),
        GameEvent::SupportPlaced { player, slot, card_id, durability } => (
            "support_placed",
            serde_json::json!({
                "player": player.0 + 1,
                "slot": slot.0,
                "cardId": card_id.0,
                "durability": durability
            }),
        ),
        GameEvent::SupportDurabilityChanged { player, slot, old_durability, new_durability } => (
            "support_durability_changed",
            serde_json::json!({
                "player": player.0 + 1,
                "slot": slot.0,
                "oldDurability": old_durability,
                "newDurability": new_durability
            }),
        ),
        GameEvent::SupportRemoved { player, slot, card_id, reason } => (
            "support_removed",
            serde_json::json!({
                "player": player.0 + 1,
                "slot": slot.0,
                "cardId": card_id.0,
                "reason": format!("{:?}", reason)
            }),
        ),
        GameEvent::EssenceChanged { player, old_essence, new_essence, max_essence } => (
            "essence_changed",
            serde_json::json!({
                "player": player.0 + 1,
                "old": old_essence,
                "new": new_essence,
                "max": max_essence
            }),
        ),
        GameEvent::ActionPointsChanged { player, old_ap, new_ap } => (
            "action_points_changed",
            serde_json::json!({
                "player": player.0 + 1,
                "oldAp": old_ap,
                "newAp": new_ap
            }),
        ),
        GameEvent::AbilityTriggered { source_player, source_slot, card_id, trigger_name } => (
            "ability_triggered",
            serde_json::json!({
                "player": source_player.0 + 1,
                "slot": source_slot.0,
                "cardId": card_id.0,
                "triggerName": trigger_name
            }),
        ),
        GameEvent::SpellEffectApplied { card_id, effect_description } => (
            "spell_effect_applied",
            serde_json::json!({
                "cardId": card_id.0,
                "effectDescription": effect_description
            }),
        ),
    };

    GameEventDto {
        event_type: event_type.to_string(),
        data,
    }
}
