# -*- coding: utf-8 -*-
"""
KARDS 完整版 —— Python + Tkinter
运行: python kards_full.py
"""
import tkinter as tk
from tkinter import messagebox
import random, platform
from dataclasses import dataclass, field
from typing import Optional, List, Tuple, Callable, Any

# ═══════════════════════ 基础配置 ═══════════════════════
_SYS = platform.system()
FONT = {"Windows": "Microsoft YaHei", "Darwin": "PingFang SC"}.get(_SYS, "Noto Sans CJK SC")

HQ_MAX, MAX_MANA, MAX_HAND = 20, 12, 9
SUP_CAP, FRONT_CAP, DECK_SIZE, ALLY_LIMIT = 4, 5, 40, 12
RARITY_LIMIT = {"standard": 4, "limited": 3, "special": 2, "elite": 1}

FACTION_NAME = {"germany": "德国", "soviet": "苏联", "usa": "美国", "uk": "英国"}
UTYPE_NAME = {"infantry": "步兵", "tank": "坦克", "artillery": "火炮",
              "fighter": "战斗机", "bomber": "轰炸机"}
KW_NAME = {"guard": "守护", "ambush": "伏击", "fury": "奋战", "blitz": "闪击",
           "shock": "冲击", "smokescreen": "烟幕",
           "heavy_armor_1": "重甲1", "heavy_armor_2": "重甲2"}

C = {
    "bg": "#0d1117", "panel": "#161b22", "panel2": "#1f242c",
    "front": "#21262d", "card": "#2d3748",
    "text": "#e6edf3", "dim": "#8b949e",
    "atk": "#f85149", "hp": "#3fb950", "mana": "#58a6ff",
    "select": "#d29922", "target": "#f85149", "ok": "#3fb950",
    "disabled": "#0a0c10",
    "germany": "#4a5568", "soviet": "#9b2c2c", "usa": "#2b6cb0", "uk": "#276749",
}


# ═══════════════════════ 数据模型 ═══════════════════════
@dataclass(eq=False)
class CardDef:
    name: str
    cost: int
    faction: str
    rarity: str
    card_type: str = "unit"           # unit / order / counter / hq
    unit_type: str = ""
    attack: int = 0
    hp: int = 0
    action_cost: int = 1
    keywords: tuple = ()
    text: str = ""
    effect: Optional[Callable] = None       # 指令效果 / 部署效果 / 反制效果
    target_type: str = "none"               # none/enemy_unit/friendly_unit/any_unit
    trigger: str = ""                        # 反制触发时机


@dataclass(eq=False)
class Unit:
    card: CardDef
    owner: int
    attack: int
    hp: int
    max_hp: int
    line: str = "support"                    # support / front
    ready: bool = False
    attacks_left: int = 1
    has_moved: bool = False
    has_attacked: bool = False
    ambush_ready: bool = False
    shock_used: bool = False
    smokescreen: bool = False
    keywords: set = field(default_factory=set)
    death_effect: Optional[Callable] = None

    @property
    def name(self): return self.card.name
    @property
    def unit_type(self): return self.card.unit_type

    @property
    def heavy_armor(self):
        for k in self.keywords:
            if k.startswith("heavy_armor_"):
                return int(k.rsplit("_", 1)[1])
        return 0

    @property
    def has_guard(self): return "guard" in self.keywords


def make_unit(card: CardDef, owner: int) -> Unit:
    u = Unit(card, owner, card.attack, card.hp, card.hp)
    u.keywords = set(card.keywords)
    u.ambush_ready = "ambush" in u.keywords
    u.smokescreen = "smokescreen" in u.keywords
    u.attacks_left = 2 if "fury" in u.keywords else 1
    u.death_effect = getattr(card, "death_effect", None)
    return u


# ═══════════════════════ 效果工厂 ═══════════════════════
def eff_dmg_unit(n):
    def f(g, pi, tgt, ctx=None):
        if isinstance(tgt, Unit):
            g.damage(tgt, n)
            g.log(f"  对 {tgt.name} 造成 {n} 点伤害")
    return f


def eff_dmg_all_enemy(n):
    def f(g, pi, tgt=None, ctx=None):
        opp = 1 - pi
        for u in list(g.player_units(opp)):
            g.damage(u, n)
        g.log(f"  对所有敌方单位造成 {n} 点伤害")
        g.cleanup()
    return f


def eff_heal_hq(n):
    def f(g, pi, tgt=None, ctx=None):
        p = g.players[pi]
        p.hq_hp = min(HQ_MAX, p.hq_hp + n)
        g.log(f"  总部恢复 {n} 点防御")
    return f


def eff_draw(n):
    def f(g, pi, tgt=None, ctx=None):
        g.draw(pi, n)
        g.log(f"  抽 {n} 张牌")
    return f


def eff_buff_unit(atk, hp):
    def f(g, pi, tgt, ctx=None):
        if isinstance(tgt, Unit):
            tgt.attack += atk
            tgt.max_hp += hp
            tgt.hp += hp
            g.log(f"  {tgt.name} +{atk}/+{hp}")
    return f


def eff_buff_all(atk, hp):
    def f(g, pi, tgt=None, ctx=None):
        for u in g.player_units(pi):
            u.attack += atk
            u.max_hp += hp
            u.hp += hp
        g.log(f"  全体友方单位 +{atk}/+{hp}")
    return f


def eff_buff_type(utype, atk, hp):
    def f(g, pi, tgt=None, ctx=None):
        for u in g.player_units(pi):
            if u.unit_type == utype:
                u.attack += atk
                u.max_hp += hp
                u.hp += hp
        g.log(f"  所有友方{UTYPE_NAME[utype]} +{atk}/+{hp}")
    return f


def eff_blitz_buff(atk):
    def f(g, pi, tgt, ctx=None):
        if isinstance(tgt, Unit):
            tgt.attack += atk
            tgt.ready = True
            tgt.has_moved = False
            tgt.has_attacked = False
            tgt.attacks_left = max(tgt.attacks_left, 1)
            g.log(f"  {tgt.name} +{atk}/+0 并可立即行动")
    return f


def eff_heal_and_draw(g, pi, tgt=None, ctx=None):
    g.draw(pi, 1)
    p = g.players[pi]
    p.hq_hp = min(HQ_MAX, p.hq_hp + 3)
    g.log("  抽 1 张牌，总部恢复 3 点")


def eff_summon(n, utype, atk, hp):
    def f(g, pi, tgt=None, ctx=None):
        p = g.players[pi]
        for _ in range(n):
            if len(p.support) >= SUP_CAP:
                break
            fake = CardDef(name=f"征召兵", cost=0, faction=p.faction,
                           rarity="standard", card_type="unit",
                           unit_type=utype, attack=atk, hp=hp)
            u = make_unit(fake, pi)
            p.support.append(u)
        g.log(f"  召唤 {n} 个 {UTYPE_NAME[utype]}（{atk}/{hp}）")
    return f


def eff_heal_all(n):
    def f(g, pi, tgt=None, ctx=None):
        for u in g.player_units(pi):
            u.hp = min(u.max_hp, u.hp + n)
        g.log(f"  所有友方单位恢复 {n} 点")
    return f


# ═══════════════════════ 卡池 ═══════════════════════
ALL_CARDS: List[CardDef] = []


def _add(*cards): ALL_CARDS.extend(cards)


def UC(name, cost, fac, rar, ut, a, h, act=1, kw=(), effect=None, text=""):
    return CardDef(name=name, cost=cost, faction=fac, rarity=rar,
                   card_type="unit", unit_type=ut, attack=a, hp=h,
                   action_cost=act, keywords=kw, effect=effect, text=text,
                   target_type="friendly_unit" if effect else "none")


def OC(name, cost, fac, rar, text, effect, tgt="none"):
    return CardDef(name=name, cost=cost, faction=fac, rarity=rar,
                   card_type="order", text=text, effect=effect, target_type=tgt)


def CC(name, cost, fac, rar, text, effect, trigger):
    return CardDef(name=name, cost=cost, faction=fac, rarity=rar,
                   card_type="counter", text=text, effect=effect, trigger=trigger)


# ─── 德国 ───
_add(
    UC("装甲掷弹兵", 2, "germany", "standard", "infantry", 2, 3, 1),
    UC("三号坦克", 3, "germany", "standard", "tank", 3, 3, 1),
    UC("Bf 109", 3, "germany", "standard", "fighter", 3, 2, 1),
    OC("闪电战", 2, "germany", "standard", "使一个友方单位 +2/+0 并可立即行动",
       eff_blitz_buff(2), "friendly_unit"),
    OC("铁拳", 1, "germany", "standard", "对一个单位造成 3 点伤害",
       eff_dmg_unit(3), "any_unit"),
    UC("88毫米高炮", 4, "germany", "limited", "artillery", 3, 3, 1),
    UC("斯图卡", 4, "germany", "limited", "bomber", 4, 2, 2, ("ambush",)),
    UC("空降兵", 3, "germany", "limited", "infantry", 3, 2, 1, ("blitz",)),
    UC("88炮阵地", 5, "germany", "special", "artillery", 4, 4, 2, ("heavy_armor_1",)),
    UC("虎式坦克", 6, "germany", "elite", "tank", 6, 6, 2, ("heavy_armor_1",)),
    OC("闪电突击", 3, "germany", "limited", "对所有敌方单位造成 2 点伤害",
       eff_dmg_all_enemy(2)),
    UC("隆美尔", 5, "germany", "special", "infantry", 4, 4, 1,
       effect=eff_buff_all(1, 1), text="部署：全体友方单位 +1/+1"),
    UC("反坦克炮", 3, "germany", "standard", "artillery", 3, 3, 1, ("ambush",)),
    CC("U艇", 4, "germany", "special", "反制：敌方使用指令时，对其总部造成 3 点伤害",
       eff_heal_hq(0), "enemy_order"),
)

# ─── 苏联 ───
_add(
    UC("征召兵", 1, "soviet", "standard", "infantry", 2, 2, 1),
    UC("T-34", 4, "soviet", "standard", "tank", 4, 4, 2),
    UC("雅克-9", 3, "soviet", "standard", "fighter", 3, 3, 1),
    OC("政委", 1, "soviet", "standard", "抽 2 张牌", eff_draw(2)),
    OC("乌拉冲锋", 2, "soviet", "standard", "全体友方单位 +1/+1",
       eff_buff_all(1, 1)),
    UC("喀秋莎", 4, "soviet", "limited", "artillery", 4, 2, 2),
    UC("伊尔-2", 5, "soviet", "limited", "bomber", 5, 3, 2),
    UC("近卫步兵", 3, "soviet", "limited", "infantry", 3, 4, 1, ("guard",)),
    UC("反坦克炮", 3, "soviet", "special", "artillery", 3, 3, 1, ("ambush",)),
    OC("斯大林格勒", 4, "soviet", "elite", "总部恢复 8 点防御", eff_heal_hq(8)),
    UC("巷战专家", 3, "soviet", "limited", "infantry", 4, 2, 1, ("fury",)),
    UC("KV-1", 5, "soviet", "special", "tank", 4, 6, 2, ("heavy_armor_1",)),
    OC("冬季反攻", 3, "soviet", "limited", "对所有敌方单位造成 2 点伤害",
       eff_dmg_all_enemy(2)),
    CC("哨兵", 2, "soviet", "standard", "反制：敌方部署单位时，对其造成 2 点伤害",
       eff_dmg_unit(2), "enemy_deploy"),
)

# ─── 美国 ───
_add(
    UC("步兵连", 2, "usa", "standard", "infantry", 2, 3, 1),
    UC("谢尔曼", 4, "usa", "standard", "tank", 4, 3, 1),
    UC("P-51", 4, "usa", "standard", "fighter", 4, 3, 1),
    OC("兵工厂", 2, "usa", "standard", "抽 1 张牌，总部恢复 3 点",
       eff_heal_and_draw),
    UC("B-17", 6, "usa", "limited", "bomber", 6, 4, 2),
    UC("巴顿", 5, "usa", "special", "tank", 5, 4, 1, ("blitz",)),
    UC("空降兵", 3, "usa", "standard", "infantry", 3, 3, 1, ("blitz",)),
    UC("谢尔曼萤火虫", 4, "usa", "limited", "tank", 5, 3, 1),
    OC("空袭", 4, "usa", "limited", "对所有敌方单位造成 3 点伤害",
       eff_dmg_all_enemy(3)),
    OC("生产线", 3, "usa", "special", "抽 3 张牌", eff_draw(3)),
    UC("无畏轰炸机", 5, "usa", "limited", "bomber", 5, 4, 2),
    UC("反坦克炮", 3, "usa", "standard", "artillery", 3, 2, 1, ("ambush",)),
    CC("太平洋潜艇", 4, "usa", "special", "反制：敌方攻击总部时，对其总部造成 4 点伤害",
       eff_heal_hq(0), "enemy_attack_hq"),
    OC("M1加兰德", 1, "usa", "standard", "所有友方步兵 +2/+2",
       eff_buff_type("infantry", 2, 2)),
)

# ─── 英国 ───
_add(
    UC("皇家步兵", 2, "uk", "standard", "infantry", 2, 4, 1),
    UC("喷火", 4, "uk", "standard", "fighter", 4, 2, 1),
    UC("丘吉尔", 5, "uk", "limited", "tank", 4, 6, 2, ("heavy_armor_1",)),
    UC("兰开斯特", 5, "uk", "limited", "bomber", 5, 3, 2),
    UC("飓风", 3, "uk", "standard", "fighter", 3, 3, 1),
    UC("蒙哥马利", 4, "uk", "special", "infantry", 3, 4, 1,
       effect=eff_buff_all(1, 0), text="部署：全体友方单位 +1/+0"),
    UC("皇家炮兵", 4, "uk", "standard", "artillery", 4, 3, 1),
    CC("雷达站", 2, "uk", "standard", "反制：敌方使用指令时，抽 2 张牌",
       eff_draw(2), "enemy_order"),
    UC("马蒂尔达", 4, "uk", "limited", "tank", 3, 5, 2, ("heavy_armor_1",)),
    UC("喷火Mk.IX", 5, "uk", "special", "fighter", 5, 3, 1, ("ambush",)),
    OC("空降", 3, "uk", "limited", "召唤 2 个 1/2 步兵",
       eff_summon(2, "infantry", 1, 2)),
    UC("英王乔治五世", 6, "uk", "elite", "tank", 6, 6, 2, ("heavy_armor_1",)),
    UC("防空炮", 4, "uk", "limited", "artillery", 3, 4, 1, ("guard",)),
    OC("空投补给", 2, "uk", "standard", "所有友方单位恢复 2 点",
       eff_heal_all(2)),
)

HQ_CARDS = {f: CardDef(name=f"{FACTION_NAME[f]}总部", cost=0, faction=f,
                        rarity="hq", card_type="hq")
            for f in FACTION_NAME}


def build_deck(main_f: str, ally_f: str) -> List[CardDef]:
    rng = random.Random()
    def pool(f):
        result = []
        for c in ALL_CARDS:
            if c.faction == f:
                result.extend([c] * RARITY_LIMIT[c.rarity])
        rng.shuffle(result)
        return result
    main_pool, ally_pool = pool(main_f), pool(ally_f)
    deck = [HQ_CARDS[main_f]]
    deck += ally_pool[:min(ALLY_LIMIT, len(ally_pool))]
    deck += main_pool[:DECK_SIZE - len(deck)]
    rng.shuffle(deck)
    return deck


# ═══════════════════════ 玩家状态 ═══════════════════════
@dataclass
class PlayerState:
    name: str
    faction: str
    is_ai: bool = False
    hq_hp: int = HQ_MAX
    mana: int = 0
    mana_max: int = 0
    deck: List[CardDef] = field(default_factory=list)
    hand: List[CardDef] = field(default_factory=list)
    support: List[Unit] = field(default_factory=list)
    counters: List[CardDef] = field(default_factory=list)
    fatigue: int = 0


# ═══════════════════════ 核心游戏逻辑 ═══════════════════════
class Game:
    def __init__(self, decks, names, factions, ai_flags):
        self.players = [
            PlayerState(names[i], factions[i], ai_flags[i], deck=list(decks[i]))
            for i in range(2)
        ]
        self.frontline: List[Unit] = []
        self.frontline_owner: Optional[int] = None
        self.turn = 0
        self.turn_number = 0
        self.game_over = False
        self.winner: Optional[int] = None
        self.logs: List[str] = []

    # ─── 基础工具 ───
    def log(self, msg):
        self.logs.append(msg)
        if len(self.logs) > 300:
            self.logs = self.logs[-300:]

    def player_units(self, pi):
        return list(self.players[pi].support) + \
               [u for u in self.frontline if u.owner == pi]

    def damage(self, unit: Unit, amount: int):
        unit.hp -= amount

    def combat_damage(self, unit: Unit, amount: int):
        unit.hp -= max(0, amount - unit.heavy_armor)

    def draw(self, pi, n=1):
        p = self.players[pi]
        for _ in range(n):
            if not p.deck:
                p.fatigue += 1
                p.hq_hp -= p.fatigue
                self.log(f"{p.name} 牌库已空，受到 {p.fatigue} 点疲劳伤害")
                continue
            c = p.deck.pop(0)
            if len(p.hand) >= MAX_HAND:
                self.log(f"{p.name} 手牌已满，{c.name} 被弃置")
                continue
            p.hand.append(c)

    def cleanup(self):
        dead = []
        for p in self.players:
            for u in list(p.support):
                if u.hp <= 0:
                    p.support.remove(u)
                    dead.append(u)
        for u in list(self.frontline):
            if u.hp <= 0:
                self.frontline.remove(u)
                dead.append(u)
        if not self.frontline:
            self.frontline_owner = None
        for u in dead:
            self.log(f"{u.name} 被消灭")
            if u.death_effect:
                u.death_effect(self, u.owner, None)

    def check_win(self) -> bool:
        d0, d1 = self.players[0].hq_hp <= 0, self.players[1].hq_hp <= 0
        if d0 and d1:
            self.winner = 1 - self.turn
            self.game_over = True
        elif d0:
            self.winner, self.game_over = 1, True
        elif d1:
            self.winner, self.game_over = 0, True
        return self.game_over

    # ─── 回合流程 ───
    def start_game(self):
        rng = random.Random()
        for i in range(2):
            p = self.players[i]
            rng.shuffle(p.deck)
            n = 4 if i == 0 else 5
            p.hand = p.deck[:n]
            p.deck = p.deck[n:]
            p.mana = p.mana_max = 0

    def begin_turn(self, pi):
        p = self.players[pi]
        self.turn = pi
        self.turn_number += 1
        p.mana_max = min(MAX_MANA, p.mana_max + 1)
        p.mana = p.mana_max
        for u in self.player_units(pi):
            u.ready = True
            u.has_moved = False
            u.has_attacked = False
            u.attacks_left = 2 if "fury" in u.keywords else 1
            u.shock_used = False
            if "ambush" in u.keywords:
                u.ambush_ready = True
        if self.turn_number > 1:
            self.draw(pi)
        self.check_win()

    # ─── 出牌 ───
    def play_card(self, pi, idx, target=None) -> Tuple[bool, str]:
        p = self.players[pi]
        if not (0 <= idx < len(p.hand)):
            return False, "无效手牌"
        card = p.hand[idx]
        if card.cost > p.mana:
            return False, "指挥点不足"
        if card.card_type == "unit" and len(p.support) >= SUP_CAP:
            return False, "支援阵线已满"

        if card.target_type == "enemy_unit":
            if not (isinstance(target, Unit) and target.owner != pi):
                return False, "需要选择敌方单位"
        elif card.target_type == "friendly_unit":
            if not (isinstance(target, Unit) and target.owner == pi):
                return False, "需要选择友方单位"
        elif card.target_type == "any_unit":
            if not isinstance(target, Unit):
                return False, "需要选择一个单位"

        p.mana -= card.cost
        p.hand.pop(idx)

        if card.card_type == "unit":
            u = make_unit(card, pi)
            u.ready = "blitz" in u.keywords
            p.support.append(u)
            self.log(f"{p.name} 部署 {card.name} {u.attack}/{u.hp}")
            if card.effect:
                card.effect(self, pi, u)
                self.cleanup()
        elif card.card_type == "order":
            self.log(f"{p.name} 使用指令【{card.name}】")
            if card.effect:
                card.effect(self, pi, target)
            self.cleanup()
        elif card.card_type == "counter":
            if len(p.counters) >= 3:
                return False, "反制区已满"
            p.counters.append(card)
            self.log(f"{p.name} 设置反制【{card.name}】")

        self.check_win()
        return True, "OK"

    # ─── 反制触发 ───
    def trigger_counters(self, owner: int, trigger: str, ctx: dict):
        """owner 的反制被激活（敌人触发了 trigger）"""
        p = self.players[owner]
        remaining = []
        for c in p.counters:
            if c.trigger == trigger:
                self.log(f"反制触发：【{c.name}】")
                if c.effect:
                    c.effect(self, owner, ctx.get("target"), ctx)
            else:
                remaining.append(c)
        p.counters = remaining

    # ─── 移动 ───
    def can_move(self, unit: Unit) -> bool:
        if not unit.ready or unit.line != "support" or unit.has_moved:
            return False
        if unit.has_attacked and unit.unit_type != "tank":
            return False
        if self.frontline_owner is not None and self.frontline_owner != unit.owner:
            return False
        if len(self.frontline) >= FRONT_CAP:
            return False
        return True

    def move_to_front(self, unit: Unit) -> bool:
        if not self.can_move(unit):
            return False
        self.players[unit.owner].support.remove(unit)
        self.frontline.append(unit)
        unit.line = "front"
        unit.has_moved = True
        self.frontline_owner = unit.owner
        self.log(f"{unit.name} 推进前线")
        return True

    # ─── 攻击 ───
    def can_attack(self, unit: Unit) -> bool:
        if not unit.ready or unit.attacks_left <= 0:
            return False
        if unit.has_moved and unit.unit_type != "tank":
            return False
        return True

    def legal_targets(self, unit: Unit) -> List[Any]:
        if not self.can_attack(unit):
            return []
        opp = 1 - unit.owner
        ut = unit.unit_type
        hq_ok = False

        if ut in ("infantry", "tank"):
            if unit.line == "support":
                cands = [u for u in self.frontline if u.owner == opp]
            else:
                cands = list(self.players[opp].support)
                hq_ok = True
        else:
            cands = list(self.players[opp].support) + \
                    [u for u in self.frontline if u.owner == opp]
            hq_ok = True

        cands = [u for u in cands if not u.smokescreen]

        if ut not in ("artillery", "bomber"):
            guards = [u for u in cands if u.has_guard]
            if guards:
                return guards
        result = list(cands)
        if hq_ok:
            result.append("hq")
        return result

    def attack(self, attacker: Unit, target) -> bool:
        if target not in self.legal_targets(attacker):
            return False
        attacker.attacks_left -= 1
        attacker.has_attacked = True
        self.resolve_attack(attacker, target)
        return True

    def _counter(self, attacker: Unit, defender: Unit):
        dmg = max(0, defender.attack - attacker.heavy_armor)
        if dmg > 0:
            attacker.hp -= dmg
            self.log(f"  反击：{defender.name} → {attacker.name}，{dmg} 点")

    def resolve_attack(self, attacker: Unit, target):
        # 反制：敌袭总部
        if target == "hq":
            self.trigger_counters(1 - attacker.owner, "enemy_attack_hq",
                                  {"attacker": attacker})

        # 伏击
        if isinstance(target, Unit) and target.ambush_ready:
            target.ambush_ready = False
            self.log(f"{target.name} 触发【伏击】")
            self._counter(attacker, target)
            self.cleanup()
            if attacker.hp <= 0 or self.check_win():
                return

        # 造成伤害
        if isinstance(target, Unit):
            before = target.hp
            self.combat_damage(target, attacker.attack)
            self.log(f"{attacker.name} → {target.name}，伤害 {before - target.hp}")
        else:
            self.players[1 - attacker.owner].hq_hp -= attacker.attack
            self.log(f"{attacker.name} → 敌方总部，伤害 {attacker.attack}")

        self.cleanup()
        if self.check_win():
            return

        # 反击
        if isinstance(target, Unit) and target.hp > 0:
            no_counter = (
                attacker.unit_type == "artillery"
                or (attacker.unit_type == "bomber" and target.unit_type != "fighter")
            )
            if "shock" in attacker.keywords and not attacker.shock_used:
                attacker.shock_used = True
                self.log(f"{attacker.name} 触发【冲击】")
                no_counter = True
            if not no_counter:
                self._counter(attacker, target)

        self.cleanup()
        self.check_win()


# ═══════════════════════ AI ═══════════════════════
class AI:
    def __init__(self, game: Game, pi: int):
        self.g = game
        self.pi = pi

    def take_turn(self):
        """返回一个生成器，每次 yield 表示一个动作，方便 GUI 逐步播放"""
        # 1) 出牌
        yield from self._play_cards()
        if self.g.game_over:
            return

        # 2) 推进前线
        for u in list(self.g.players[self.pi].support):
            if self.g.can_move(u):
                self.g.move_to_front(u)
                yield

        # 3) 攻击
        yield from self._attack()

    def _play_cards(self):
        p = self.g.players[self.pi]
        while True:
            played = False
            order = sorted(range(len(p.hand)), key=lambda i: -p.hand[i].cost)
            for i in order:
                if i >= len(p.hand):
                    continue
                card = p.hand[i]
                if card.cost > p.mana:
                    continue
                tgt = self._card_target(card)
                if card.target_type != "none" and tgt is None:
                    continue
                ok, _ = self.g.play_card(self.pi, i, tgt)
                if ok:
                    played = True
                    yield
                    break
            if not played or self.g.game_over:
                break

    def _card_target(self, card: CardDef):
        if card.target_type == "none":
            return None
        if card.target_type == "friendly_unit":
            mine = self.g.player_units(self.pi)
            return max(mine, key=lambda u: u.attack) if mine else None
        enemies = self.g.player_units(1 - self.pi)
        if not enemies:
            return None
        return max(enemies, key=lambda u: u.attack)

    def _attack(self):
        p = self.g.players[self.pi]
        for u in list(self.g.player_units(self.pi)):
            guard = 0
            while self.g.can_attack(u) and guard < 4:
                guard += 1
                targets = self.g.legal_targets(u)
                if not targets:
                    break
                t = self._attack_target(u, targets)
                if t is None:
                    break
                if not self.g.attack(u, t):
                    break
                yield
                if self.g.game_over:
                    return

    def _attack_target(self, attacker: Unit, targets):
        killable = [t for t in targets
                    if isinstance(t, Unit) and t.hp <= attacker.attack]
        if killable:
            return max(killable, key=lambda u: u.attack)
        units = [t for t in targets if isinstance(t, Unit)]
        if units:
            safe = [u for u in units
                    if u.attack < attacker.hp + attacker.heavy_armor]
            if safe:
                return max(safe, key=lambda u: u.attack)
            if "hq" in targets:
                return "hq"
            return max(units, key=lambda u: u.attack)
        if "hq" in targets:
            return "hq"
        return targets[0] if targets else None


# ═══════════════════════ GUI ═══════════════════════
class KardsApp:
    def __init__(self, root):
        self.root = root
        self.root.title("KARDS · 完整版")
        self.root.geometry("1240x900")
        self.root.configure(bg=C["bg"])
        self.root.resizable(False, False)

        self.game: Optional[Game] = None
        self.selected_unit: Optional[Unit] = None
        self.selected_card_idx: Optional[int] = None
        self.pending_card: Optional[Tuple[int, CardDef]] = None
        self.highlighted: List[Unit] = []
        self.can_attack_hq = False
        self.message = ""
        self.ai_gen = None
        self.busy = False

        self._build_layout()
        self.root.after(100, self.new_game)

    # ─── 界面骨架 ───
    def _build_layout(self):
        wrap = tk.Frame(self.root, bg=C["bg"])
        wrap.pack(fill="both", expand=True, padx=10, pady=8)

        self.opp_bar = tk.Frame(wrap, bg=C["panel"], height=64)
        self.opp_bar.pack(fill="x", pady=(0, 3))
        self.opp_bar.pack_propagate(False)

        self.opp_support = tk.Frame(wrap, bg=C["panel"], height=124)
        self.opp_support.pack(fill="x", pady=2)
        self.opp_support.pack_propagate(False)

        self.front_frame = tk.Frame(wrap, bg=C["front"], height=124)
        self.front_frame.pack(fill="x", pady=2)
        self.front_frame.pack_propagate(False)

        self.my_support = tk.Frame(wrap, bg=C["panel"], height=124)
        self.my_support.pack(fill="x", pady=2)
        self.my_support.pack_propagate(False)

        self.my_bar = tk.Frame(wrap, bg=C["panel"], height=64)
        self.my_bar.pack(fill="x", pady=(3, 2))
        self.my_bar.pack_propagate(False)

        self.hand_frame = tk.Frame(wrap, bg=C["bg"], height=160)
        self.hand_frame.pack(fill="x", pady=(4, 0))
        self.hand_frame.pack_propagate(False)

        bottom = tk.Frame(wrap, bg=C["bg"], height=48)
        bottom.pack(fill="x", pady=(6, 0))
        bottom.pack_propagate(False)

        self.msg_label = tk.Label(bottom, text="", bg=C["bg"], fg=C["dim"],
                                  font=(FONT, 10), anchor="w")
        self.msg_label.pack(side="left", fill="x", expand=True)

        self.log_btn = tk.Button(bottom, text="战报", font=(FONT, 10),
                                 bg="#2d3748", fg=C["text"], width=8,
                                 command=self.show_log)
        self.log_btn.pack(side="right", padx=3)

        self.move_btn = tk.Button(bottom, text="推进前线", font=(FONT, 10, "bold"),
                                  bg="#2d3748", fg=C["text"], width=10,
                                  state="disabled", command=self.on_move)
        self.move_btn.pack(side="right", padx=3)

        self.end_btn = tk.Button(bottom, text="结束回合", font=(FONT, 10, "bold"),
                                 bg="#2b6cb0", fg="white", width=10,
                                 command=self.on_end_turn)
        self.end_btn.pack(side="right", padx=3)

    @staticmethod
    def _clear(frame):
        for w in frame.winfo_children():
            w.destroy()

    # ─── 单位控件 ───
    def _unit_widget(self, parent, unit: Unit):
        is_mine = (unit.owner == 0)
        bg = C.get(unit.card.faction, C["card"])
        if not unit.ready and is_mine:
            bg = "#1c2228"

        border, thick = "#333", 2
        if unit == self.selected_unit:
            border, thick = C["select"], 3
        elif unit in self.highlighted:
            border, thick = C["target"], 3

        f = tk.Frame(parent, bg=bg, width=96, height=112,
                     highlightthickness=thick, highlightbackground=border)
        f.pack(side="left", padx=4, pady=6)
        f.pack_propagate(False)

        tk.Label(f, text=unit.name, bg=bg, fg=C["text"],
                 font=(FONT, 9, "bold"), wraplength=90).pack(pady=(4, 0))
        tk.Label(f, text=UTYPE_NAME.get(unit.unit_type, ""), bg=bg,
                 fg=C["dim"], font=(FONT, 8)).pack()

        kws = [KW_NAME[k] for k in ("guard", "ambush", "fury", "blitz", "shock", "smokescreen")
               if k in unit.keywords]
        if unit.heavy_armor:
            kws.append(f"重甲{unit.heavy_armor}")
        if kws:
            tk.Label(f, text=" ".join(kws), bg=bg, fg=C["select"],
                     font=(FONT, 7)).pack()

        stats = tk.Frame(f, bg=bg)
        stats.pack(side="bottom", fill="x", pady=3)
        tk.Label(stats, text=f"⚔{unit.attack}", bg=bg, fg=C["atk"],
                 font=(FONT, 10, "bold")).pack(side="left", padx=6)
        tk.Label(stats, text=f"♥{unit.hp}", bg=bg, fg=C["hp"],
                 font=(FONT, 10, "bold")).pack(side="right", padx=6)

        if is_mine:
            tk.Label(f, text="●", bg=bg,
                     fg="#3fb950" if unit.ready else "#555",
                     font=(FONT, 8)).place(x=4, y=4)

        def click(_e): self.on_unit_click(unit)
        for w in [f] + list(f.winfo_children()):
            w.bind("<Button-1>", click)
        return f

    # ─── 手牌控件 ───
    def _hand_widget(self, parent, card: CardDef, idx):
        p = self.game.players[0]
        playable = (card.cost <= p.mana
                    and not (card.card_type == "unit" and len(p.support) >= SUP_CAP)
                    and self.game.turn == 0
                    and not self.game.game_over
                    and not self.busy)
        bg = C.get(card.faction, C["card"]) if playable else C["disabled"]
        border, thick = "#333", 2
        if self.selected_card_idx == idx:
            border, thick = C["select"], 3

        f = tk.Frame(parent, bg=bg, width=116, height=150,
                     highlightthickness=thick, highlightbackground=border)
        f.pack(side="left", padx=3, pady=4)
        f.pack_propagate(False)

        tk.Label(f, text=str(card.cost), bg="#1a202c", fg=C["mana"],
                 font=(FONT, 11, "bold"), width=2).place(x=3, y=3)

        if card.card_type == "counter":
            tk.Label(f, text="反制", bg="#4a2c2c", fg="#ffd9d9",
                     font=(FONT, 7), padx=3).place(x=64, y=5)

        tk.Label(f, text=card.name, bg=bg, fg=C["text"],
                 font=(FONT, 9, "bold"), wraplength=104).pack(pady=(22, 2))

        if card.card_type == "unit":
            tk.Label(f, text=f"{UTYPE_NAME.get(card.unit_type, '')}  {card.attack}/{card.hp}",
                     bg=bg, fg=C["dim"], font=(FONT, 8)).pack()
            if card.keywords:
                kws = " ".join(KW_NAME.get(k, k) for k in card.keywords)
                tk.Label(f, text=kws, bg=bg, fg=C["select"],
                         font=(FONT, 7)).pack()
        else:
            tk.Label(f, text="指令", bg=bg, fg=C["dim"],
                     font=(FONT, 8)).pack()

        if card.text:
            tk.Label(f, text=card.text, bg=bg, fg=C["text"],
                     font=(FONT, 7), wraplength=104,
                     justify="left").pack(side="bottom", pady=4, padx=4)

        def click(_e, i=idx): self.on_hand_click(i)
        for w in [f] + list(f.winfo_children()):
            w.bind("<Button-1>", click)
        return f

    # ─── 信息栏 ───
    def _build_opp_bar(self):
        self._clear(self.opp_bar)
        p = self.game.players[1]
        bg = "#3a1a1a"
        hq = tk.Frame(self.opp_bar, bg=bg, padx=12, pady=4,
                      highlightthickness=2,
                      highlightbackground=C["target"] if self.can_attack_hq else "#555")
        hq.pack(side="left", padx=8, pady=6)
        tk.Label(hq, text="敌方总部", bg=bg, fg=C["dim"],
                 font=(FONT, 8)).pack()
        tk.Label(hq, text=f"♥ {max(0, p.hq_hp)}", bg=bg, fg=C["hp"],
                 font=(FONT, 14, "bold")).pack()
        if self.can_attack_hq:
            for w in [hq] + list(hq.winfo_children()):
                w.bind("<Button-1>", lambda e: self.on_hq_click())

        info = tk.Frame(self.opp_bar, bg=C["panel"])
        info.pack(side="left", padx=12)
        tk.Label(info, text=f"AI 指挥官 · {FACTION_NAME[p.faction]}",
                 bg=C["panel"], fg=C["text"],
                 font=(FONT, 11, "bold")).pack(anchor="w")
        tk.Label(info, text=f"手牌 {len(p.hand)}  牌库 {len(p.deck)}  "
                           f"反制 {len(p.counters)}",
                 bg=C["panel"], fg=C["dim"], font=(FONT, 9)).pack(anchor="w")

        right = tk.Frame(self.opp_bar, bg=C["panel"])
        right.pack(side="right", padx=12)
        tk.Label(right, text=f"指挥点 {p.mana}/{p.mana_max}",
                 bg=C["panel"], fg=C["mana"],
                 font=(FONT, 11, "bold")).pack()

    def _build_my_bar(self):
        self._clear(self.my_bar)
        p = self.game.players[0]
        bg = "#1a3a1a"
        hq = tk.Frame(self.my_bar, bg=bg, padx=12, pady=4,
                      highlightthickness=2, highlightbackground="#555")
        hq.pack(side="left", padx=8, pady=6)
        tk.Label(hq, text="我方总部", bg=bg, fg=C["dim"],
                 font=(FONT, 8)).pack()
        tk.Label(hq, text=f"♥ {max(0, p.hq_hp)}", bg=bg, fg=C["hp"],
                 font=(FONT, 14, "bold")).pack()

        info = tk.Frame(self.my_bar, bg=C["panel"])
        info.pack(side="left", padx=12)
        tk.Label(info, text=f"你 · {FACTION_NAME[p.faction]}",
                 bg=C["panel"], fg=C["text"],
                 font=(FONT, 11, "bold")).pack(anchor="w")
        tk.Label(info, text=f"牌库 {len(p.deck)}  反制 {len(p.counters)}",
                 bg=C["panel"], fg=C["dim"], font=(FONT, 9)).pack(anchor="w")

        right = tk.Frame(self.my_bar, bg=C["panel"])
        right.pack(side="right", padx=12)
        tk.Label(right, text=f"指挥点 {p.mana}/{p.mana_max}",
                 bg=C["panel"], fg=C["mana"],
                 font=(FONT, 11, "bold")).pack()

    # ─── 刷新 ───
    def refresh(self):
        if not self.game:
            return

        self._clear(self.opp_support)
        for u in self.game.players[1].support:
            self._unit_widget(self.opp_support, u)

        self._clear(self.front_frame)
        owner = ("你控制" if self.game.frontline_owner == 0
                 else "AI控制" if self.game.frontline_owner == 1
                 else "无归属")
        tk.Label(self.front_frame, text=f"前 线 · {owner}",
                 bg=C["front"], fg=C["dim"],
                 font=(FONT, 8)).pack(side="left", padx=8)
        for u in self.game.frontline:
            self._unit_widget(self.front_frame, u)

        self._clear(self.my_support)
        for u in self.game.players[0].support:
            self._unit_widget(self.my_support, u)

        self._build_opp_bar()
        self._build_my_bar()

        self._clear(self.hand_frame)
        for i, card in enumerate(self.game.players[0].hand):
            self._hand_widget(self.hand_frame, card, i)

        can_move = (self.selected_unit is not None
                    and self.game.turn == 0 and not self.busy
                    and not self.game.game_over
                    and self.game.can_move(self.selected_unit))
        self.move_btn.config(state="normal" if can_move else "disabled")
        can_end = (self.game.turn == 0 and not self.busy
                   and not self.game.game_over)
        self.end_btn.config(state="normal" if can_end else "disabled")

        self.msg_label.config(text=self.message)

    # ─── 高亮 ───
    def _update_highlights(self):
        self.highlighted = []
        self.can_attack_hq = False
        if self.pending_card is not None:
            _, card = self.pending_card
            if card.target_type in ("enemy_unit", "any_unit"):
                self.highlighted.extend(self.game.player_units(1))
            if card.target_type in ("friendly_unit", "any_unit"):
                self.highlighted.extend(self.game.player_units(0))
        elif self.selected_unit is not None:
            targets = self.game.legal_targets(self.selected_unit)
            self.highlighted = [t for t in targets if isinstance(t, Unit)]
            self.can_attack_hq = "hq" in targets

    def _clear_selection(self):
        self.selected_unit = None
        self.selected_card_idx = None
        self.pending_card = None
        self.highlighted = []
        self.can_attack_hq = False

    # ─── 点击事件 ───
    def on_hand_click(self, idx):
        if self.busy or not self.game or self.game.game_over or self.game.turn != 0:
            return
        if self.selected_card_idx == idx and self.pending_card is None:
            self._clear_selection(); self.refresh(); return

        p = self.game.players[0]
        card = p.hand[idx]
        if card.cost > p.mana:
            self.message = "× 指挥点不足"; self.refresh(); return
        if card.card_type == "unit" and len(p.support) >= SUP_CAP:
            self.message = "× 支援阵线已满（最多 4 个单位）"
            self.refresh(); return

        self.selected_unit = None
        self.selected_card_idx = idx

        if card.target_type == "none":
            ok, msg = self.game.play_card(0, idx)
            self.selected_card_idx = None
            self.message = "" if ok else "× " + msg
            self._update_highlights()
            self._after_action()
        else:
            self.pending_card = (idx, card)
            hint = {"enemy_unit": "选择敌方单位",
                    "friendly_unit": "选择友方单位",
                    "any_unit": "选择任意单位"}[card.target_type]
            self.message = f"👉 {hint}"
            self._update_highlights()
            self.refresh()

    def _is_valid_target(self, unit: Unit):
        if not self.pending_card: return False
        _, card = self.pending_card
        if card.target_type == "enemy_unit": return unit.owner != 0
        if card.target_type == "friendly_unit": return unit.owner == 0
        return True

    def on_unit_click(self, unit: Unit):
        if self.busy or not self.game or self.game.game_over or self.game.turn != 0:
            return
        # 正在选卡牌目标
        if self.pending_card is not None:
            if self._is_valid_target(unit):
                idx, _ = self.pending_card
                ok, msg = self.game.play_card(0, idx, unit)
                self._clear_selection()
                self.message = "" if ok else "× " + msg
                self._after_action()
            else:
                self.message = "× 无效目标"
                self.refresh()
            return
        # 攻击
        if self.selected_unit is not None and unit.owner != 0:
            if unit in self.highlighted:
                self.game.attack(self.selected_unit, unit)
                self._clear_selection()
                self._after_action()
            else:
                self.message = "× 该目标不可攻击"
                self.refresh()
            return
        # 选中我方单位
        if unit.owner == 0:
            if self.selected_unit == unit:
                self._clear_selection()
            else:
                self.selected_unit = unit
                self.selected_card_idx = None
                self.message = "选中：点敌方目标攻击，或【推进前线】"
            self._update_highlights()
            self.refresh()

    def on_hq_click(self):
        if self.busy or self.game.game_over or self.game.turn != 0: return
        if self.selected_unit is not None and self.can_attack_hq:
            self.game.attack(self.selected_unit, "hq")
            self._clear_selection()
            self._after_action()

    def on_move(self):
        if self.busy or not self.selected_unit: return
        if self.game.move_to_front(self.selected_unit):
            self.selected_unit = None
            self._clear_selection()
            self._after_action()
        else:
            self.message = "× 无法推进前线"
            self.refresh()

    def _after_action(self):
        self._update_highlights()
        self.refresh()
        if self.game.game_over:
            self.busy = True
            self.root.after(500, self.show_game_over)

    # ─── 回合流转 ───
    def on_end_turn(self):
        if self.busy or not self.game or self.game.game_over or self.game.turn != 0:
            return
        self._clear_selection()
        self.message = ""
        self.busy = True
        self.refresh()
        self.root.after(200, self._start_ai_turn)

    def _start_ai_turn(self):
        self.game.begin_turn(1)
        self.refresh()
        if self.game.game_over:
            self.root.after(500, self.show_game_over); return
        self.ai_gen = AI(self.game, 1).take_turn()
        self.root.after(500, self._step_ai)

    def _step_ai(self):
        if self.game.game_over:
            self.root.after(500, self.show_game_over); return
        try:
            next(self.ai_gen)
            self.refresh()
            self.root.after(600, self._step_ai)
        except StopIteration:
            self.root.after(400, self._end_ai_turn)
        except Exception as e:
            print("AI 出错:", e)
            self.root.after(200, self._end_ai_turn)

    def _end_ai_turn(self):
        if self.game.game_over:
            self.root.after(400, self.show_game_over); return
        self.game.turn = 0
        self.game.begin_turn(0)
        self.busy = False
        self._clear_selection()
        self.message = "你的回合开始"
        self.refresh()
        if self.game.game_over:
            self.root.after(500, self.show_game_over)

    # ─── 战报 ───
    def show_log(self):
        win = tk.Toplevel(self.root)
        win.title("战报")
        win.configure(bg=C["bg"])
        win.geometry("480x520")
        txt = tk.Text(win, bg=C["panel"], fg=C["text"], font=(FONT, 10),
                      wrap="word", bd=0, padx=10, pady=10)
        txt.pack(fill="both", expand=True)
        txt.insert("1.0", "\n".join(self.game.logs[-200:]))
        txt.config(state="disabled")

    # ─── 新游戏 / 调度 ───
    def new_game(self):
        main_f = self._select_faction("选择主国")
        if not main_f: self.root.destroy(); return
        ally_f = self._select_faction("选择盟国（盟国卡 ≤ 12 张）", exclude=main_f)
        if not ally_f: self.root.destroy(); return

        player_deck = build_deck(main_f, ally_f)
        ai_main = random.choice([k for k in FACTION_NAME if k != main_f])
        ai_ally = random.choice([k for k in FACTION_NAME if k != ai_main])
        ai_deck = build_deck(ai_main, ai_ally)

        self.game = Game(
            decks=[player_deck, ai_deck],
            names=["你", "AI 指挥官"],
            factions=[main_f, ai_main],
            ai_flags=[False, True],
        )
        self.game.start_game()
        self._clear_selection()
        self.busy = True
        self.message = ""
        self.refresh()
        self._show_mulligan()

    def _select_faction(self, title, exclude=None):
        win = tk.Toplevel(self.root)
        win.title(title)
        win.configure(bg=C["bg"])
        win.geometry("360x320")
        win.transient(self.root)
        win.grab_set()
        tk.Label(win, text=title, bg=C["bg"], fg=C["text"],
                 font=(FONT, 14, "bold")).pack(pady=16)
        result = [None]
        def choose(f):
            result[0] = f
            win.destroy()
        for f, n in FACTION_NAME.items():
            if f == exclude: continue
            tk.Button(win, text=n, font=(FONT, 12, "bold"),
                      bg=C.get(f, C["card"]), fg="white",
                      activebackground=C.get(f, C["card"]),
                      activeforeground="white", width=18, relief="flat",
                      cursor="hand2",
                      command=lambda x=f: choose(x)).pack(pady=6)
        self.root.wait_window(win)
        return result[0]

    def _show_mulligan(self):
        win = tk.Toplevel(self.root)
        win.title("起手调度")
        win.configure(bg=C["bg"])
        win.geometry("720x300")
        win.transient(self.root)
        win.grab_set()
        tk.Label(win, text="选择要替换的卡牌，点确认开始",
                 bg=C["bg"], fg=C["text"],
                 font=(FONT, 12, "bold")).pack(pady=14)

        frame = tk.Frame(win, bg=C["bg"])
        frame.pack(pady=6)
        p = self.game.players[0]
        selected = set()

        for i, card in enumerate(p.hand):
            bg = C.get(card.faction, C["card"])
            f = tk.Frame(frame, bg=bg, width=100, height=150,
                         highlightthickness=2, highlightbackground="#333")
            f.pack(side="left", padx=5)
            f.pack_propagate(False)
            tk.Label(f, text=str(card.cost), bg="#1a202c", fg=C["mana"],
                     font=(FONT, 11, "bold"), width=2).place(x=3, y=3)
            tk.Label(f, text=card.name, bg=bg, fg=C["text"],
                     font=(FONT, 9, "bold"), wraplength=90).pack(pady=(24, 2))
            if card.card_type == "unit":
                tk.Label(f, text=f"{UTYPE_NAME.get(card.unit_type, '')} {card.attack}/{card.hp}",
                         bg=bg, fg=C["dim"], font=(FONT, 8)).pack()

            def toggle(_e, idx=i, fr=f):
                if idx in selected:
                    selected.remove(idx)
                    fr.config(highlightbackground="#333")
                else:
                    selected.add(idx)
                    fr.config(highlightbackground=C["select"])
            for w in [f] + list(f.winfo_children()):
                w.bind("<Button-1>", toggle)

        def confirm():
            idxs = sorted(selected, reverse=True)
            if idxs:
                swap = [p.hand.pop(k) for k in idxs if 0 <= k < len(p.hand)]
                p.deck.extend(swap)
                random.shuffle(p.deck)
                self.game.draw(0, len(swap))
            win.destroy()

        tk.Button(win, text="确认并开始", font=(FONT, 12, "bold"),
                  bg="#2b6cb0", fg="white", width=14,
                  command=confirm, cursor="hand2").pack(pady=16)
        self.root.wait_window(win)

        self.game.turn = 0
        self.game.begin_turn(0)
        self.busy = False
        self._clear_selection()
        self.message = "你的回合开始"
        self.refresh()
        if self.game.game_over:
            self.show_game_over()

    def show_game_over(self):
        self.busy = True
        if self.game.winner is None:
            msg = "游戏结束"
        else:
            w = self.game.players[self.game.winner]
            msg = f"{w.name} 获胜！\n\n你的总部：{max(0, self.game.players[0].hq_hp)}\n"
            msg += f"AI 总部：{max(0, self.game.players[1].hq_hp)}"
        if messagebox.askyesno("游戏结束", msg + "\n\n再来一局？"):
            self.new_game()
        else:
            self.root.destroy()


def main():
    root = tk.Tk()
    app = KardsApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()