//! Builder implementations and utility functions for entity DTOs.
//!
//! Covers commanders, cards, creatures, supports, and prepared effects.

use super::*;
use crate::cards::{
    AbilityDefinition, CardDefinition, CommanderDefinition, EffectDefinition,
    PassiveEffectDefinition, PassiveModifier,
};
use crate::core::cards::Faction as CardFaction;
use crate::core::effects::{EffectTarget, TargetingRule, TokenAbility, TokenEffect, Trigger};
use crate::core::state::{PreparedEffect, PreparedTrigger};
use crate::types::CardId;
use crate::{CardDatabase, CardType, Creature, Faction, Keywords, Support};

// =============================================================================
// 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",
        CardType::Ritual { .. } => "ritual",
    }
}

/// 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",
        TargetingRule::TargetEnemyCreatureOrFace => "enemy_creature_or_face",
        TargetingRule::TargetEnemySlot => "enemy_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 regular creature card abilities (Prepare / Ambush / Activated) to AbilityDto list.
///
/// Unlike token abilities, these come from the CardDefinition and are indexed the same
/// way as `generate_actions_for_abilities_card` in `legal.rs` (enumerate over all
/// abilities in the card's ability list, keeping the index even for non-usable ones).
fn card_creature_abilities_to_dtos(abilities: &[crate::core::cards::AbilityDefinition]) -> Vec<AbilityDto> {
    abilities
        .iter()
        .enumerate()
        .filter(|(_, ability)| {
            matches!(ability.trigger, Trigger::Activated | Trigger::Prepare | Trigger::Ambush)
        })
        .map(|(i, ability)| {
            let name = match ability.trigger {
                Trigger::Prepare => "Prepare".to_string(),
                Trigger::Ambush => "Ambush".to_string(),
                _ => "Ability".to_string(),
            };
            let effects_desc = if ability.effects.is_empty() {
                "Queued effect".to_string()
            } else {
                ability.effects.iter().map(describe_effect).collect::<Vec<_>>().join(". ")
            };
            let description = match ability.trigger {
                Trigger::Prepare => format!(
                    "{} — set a trap: queued now, fires automatically at the start of your next turn",
                    effects_desc
                ),
                Trigger::Ambush => format!(
                    "{} to the attacking creature — set a trap: fires automatically the first time an enemy attacks you this turn. Expires unused at turn end",
                    effects_desc
                ),
                _ => effects_desc,
            };
            AbilityDto {
                index: i as u8,
                name,
                essence_cost: ability.essence_cost,
                targeting_type: targeting_rule_to_string(&ability.targeting).to_string(),
                description,
            }
        })
        .collect()
}

/// 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",
        Trigger::Prepare => "Prepare",
        Trigger::Ambush => "Ambush",
    }
}

/// 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::BuffStatsTemporal { attack, health } => {
            format!("Buff {:+}/{:+} until end of turn", 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,
        TargetingRule::TargetEnemyCreatureOrFace => effects_text,
        TargetingRule::TargetEnemySlot => 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))
            }
            CardType::Ritual { effects, .. } => {
                let effects_text = effects.iter().map(describe_effect).collect::<Vec<_>>().join(". ");
                Some(format!("Ritual: {} — fires at the start of your next turn. Blocked by enemy creatures.", effects_text))
            }
            _ => None,
        };

        let abilities = card
            .creature_abilities()
            .map(card_creature_abilities_to_dtos)
            .unwrap_or_default();

        let (level_up_threshold, level_up_trigger, level_up_reward) = match &card.card_type {
            CardType::Creature { level_up, .. } => {
                if let Some(def) = level_up {
                    let trigger = format!("{:?}", def.trigger);
                    let reward = if def.keywords_gained.is_empty() {
                        format!("+{}/{}", def.attack_bonus, def.health_bonus)
                    } else {
                        format!("+{}/{}, gains {}", def.attack_bonus, def.health_bonus, def.keywords_gained.join(", "))
                    };
                    (Some(def.threshold), Some(trigger), Some(reward))
                } else {
                    (None, None, None)
                }
            }
            _ => (None, None, 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,
            overload_cost: card.overload_cost,
            abilities,
            level_up_threshold,
            level_up_trigger,
            level_up_reward,
        }
    }

    /// 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,
            overload_cost: None,
            abilities: Vec::new(),
            level_up_threshold: None,
            level_up_trigger: None,
            level_up_reward: None,
        }
    }
}

/// Compute the flanking display bonus: +1 per adjacent ally slot occupied, capped at 2.
/// Returns 0 if the creature doesn't have Flanking or is silenced.
fn flanking_display_bonus(creature: &Creature, all_creatures: &[Creature]) -> u8 {
    if !creature.status.has_flanking() || creature.status.is_silenced() {
        return 0;
    }
    let adjacent = creature.slot.adjacent_slots();
    all_creatures.iter().filter(|other| {
        other.slot != creature.slot && adjacent.contains(&other.slot)
    }).count() as u8
}

impl CreatureDto {
    /// Create a DTO from a creature with its card definition.
    /// `all_creatures` is the owning player's full creature list, used to compute the
    /// flanking display bonus (+1 ATK per adjacent ally) in one canonical place.
    pub fn from_creature(creature: &Creature, card: &CardDefinition, current_turn: u16, all_creatures: &[Creature]) -> Self {
        let flanking_bonus = flanking_display_bonus(creature, all_creatures);
        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));
        // Regular creatures expose Prepare/Ambush/Activated abilities; tokens use token_abilities.
        let abilities = card
            .creature_abilities()
            .map(card_creature_abilities_to_dtos)
            .unwrap_or_default();

        let level_threshold = card.level_up().map(|d| d.threshold).unwrap_or(0);
        let effective_attack = creature.attack
            .saturating_add(creature.temp_attack_bonus)
            .saturating_add(flanking_bonus as i8);

        Self {
            instance_id: creature.instance_id.0,
            card_id: card.id,
            name: card.name.clone(),
            slot: creature.slot.0,
            faction: faction.to_string(),
            attack: effective_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(),
            has_flanking: creature.status.has_flanking(),
            level: creature.level,
            level_progress: creature.progress,
            level_threshold,
            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.
    /// `all_creatures` is the owning player's full creature list for the flanking display bonus.
    pub fn from_token(creature: &Creature, current_turn: u16, all_creatures: &[Creature]) -> Self {
        let flanking_bonus = flanking_display_bonus(creature, all_creatures);
        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));
        let effective_attack = creature.attack
            .saturating_add(creature.temp_attack_bonus)
            .saturating_add(flanking_bonus as i8);

        Self {
            instance_id: creature.instance_id.0,
            card_id: 0,
            name,
            slot: creature.slot.0,
            faction,
            attack: effective_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(),
            has_flanking: creature.status.has_flanking(),
            level: creature.level,
            level_progress: creature.progress,
            level_threshold: 0, // tokens don't have level-up definitions
            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,
        }
    }
}

/// Convert a slice of PreparedEffects to DTOs for the UI.
pub fn prepared_effects_to_dtos(effects: &[PreparedEffect]) -> Vec<PreparedEffectDto> {
    effects
        .iter()
        .map(|effect| {
            let trigger = match effect.trigger {
                PreparedTrigger::StartOfNextTurn => "prepare".to_string(),
                PreparedTrigger::OnEnemyAttack => "ambush".to_string(),
            };
            let effects_text = if effect.effects.is_empty() {
                "Queued effect".to_string()
            } else {
                effect.effects.iter().map(describe_effect).collect::<Vec<_>>().join(". ")
            };
            // Append the locked-in target so both players can see what's coming
            let target_label = match &effect.target {
                Some(EffectTarget::Creature { slot, .. }) => {
                    format!(" → targeting creature in slot {}", slot.0 + 1)
                }
                Some(EffectTarget::Player(_)) => " → targeting face".to_string(),
                Some(EffectTarget::EnemySlotOrFace { slot, .. }) => {
                    format!(" → slot {} (blocks face damage if filled)", slot.0 + 1)
                }
                _ => String::new(),
            };
            let description = format!("{}{}", effects_text, target_label);
            PreparedEffectDto { trigger, description }
        })
        .collect()
}
