"""Counter-Strike 2 GSI adapter.

CS2 POSTs full state snapshots (subscribed sections) on every change, so
events are derived by diffing against the previous snapshot kept in `state`.
Player-scoped events (kills/deaths/MVP) only count while the payload's
player IS the local player — after death CS2 switches the player section to
the spectated teammate (steamid no longer matches provider.steamid), which
would otherwise credit their kills to the user.
"""

GAME = "cs2"
LABEL = "Counter-Strike 2"

CONFIG_FILENAME = "gamestate_integration_product.cfg"
CONFIG_HINT = (
    "Drop into ...\\Steam\\steamapps\\common\\Counter-Strike Global Offensive"
    "\\game\\csgo\\cfg\\ then restart CS2."
)

_MULTI = {2: "double kill", 3: "triple kill", 4: "quad kill", 5: "ACE"}


def config_text(url: str, token: str) -> str:
    return f'''"Clipshelf GSI"
{{
    "uri"       "{url}"
    "timeout"   "5.0"
    "buffer"    "0.2"
    "throttle"  "0.5"
    "heartbeat" "30.0"
    "auth"
    {{
        "token" "{token}"
    }}
    "data"
    {{
        "provider"            "1"
        "map"                 "1"
        "round"               "1"
        "player_id"           "1"
        "player_state"        "1"
        "player_match_stats"  "1"
        "player_weapons"      "1"
    }}
}}
'''


def _scores(map_):
    ct = (map_.get("team_ct") or {}).get("score")
    t = (map_.get("team_t") or {}).get("score")
    return ct, t


def parse(payload: dict, state: dict) -> list:
    events = []
    provider = payload.get("provider") or {}
    player = payload.get("player") or {}
    map_ = payload.get("map") or {}
    rnd = payload.get("round") or {}

    own = bool(player) and player.get("steamid") == provider.get("steamid")
    round_num = map_.get("round")
    pstate = (player.get("state") or {}) if own else {}
    # round_totaldmg has to be tracked continuously, NOT read live at
    # round-over: CS2 only switches the player section back to "you" at the
    # START of the next round, not at round-end — so if you died at any
    # point in the round (the normal case, not an edge case), `own` is
    # already false by the time the round-over payload arrives and a live
    # read is empty every time. Bank the last-seen value while still alive,
    # consume it at round-over, then clear it so the next round starts fresh.
    if pstate.get("round_totaldmg") is not None:
        state["round_dmg"] = pstate["round_totaldmg"]

    def emit(etype, title, **detail):
        detail.setdefault("map", map_.get("name"))
        if round_num is not None:
            detail.setdefault("round", round_num)
        events.append({"type": etype, "title": title,
                       "detail": {k: v for k, v in detail.items() if v is not None}})

    # ---- match start / end (map.phase transitions) ----
    if map_:
        phase = map_.get("phase")
        if phase == "live" and state.get("map_phase") != "live":
            emit("match_start", f"match started — {map_.get('name', '?')}")
        if phase == "gameover" and state.get("map_phase") != "gameover":
            ct, t = _scores(map_)
            score = f" (CT {ct}–{t} T)" if ct is not None else ""
            final = (player.get("match_stats") or {}) if own else {}
            my_team = player.get("team") if own else state.get("team")
            emit("match_end", f"match over — {map_.get('name', '?')}{score}",
                 score_ct=ct, score_t=t, team=my_team,
                 final_kills=final.get("kills"), final_deaths=final.get("deaths"),
                 final_assists=final.get("assists"), final_mvps=final.get("mvps"))
        state["map_phase"] = phase

    # ---- round over: win/loss from the player's perspective ----
    if rnd:
        phase = rnd.get("phase")
        if phase == "over" and state.get("round_phase") != "over":
            win_team = rnd.get("win_team")
            my_team = player.get("team") if own else state.get("team")
            ct, t = _scores(map_)
            score = f" ({ct}–{t})" if ct is not None else ""
            dmg = state.get("round_dmg")
            if win_team and my_team:
                won = win_team.upper() == my_team.upper()
                emit("round_win" if won else "round_loss",
                     f"round {'won' if won else 'lost'}{score}",
                     win_team=win_team, bomb=rnd.get("bomb"), dmg=dmg)
            elif win_team:
                emit("round_end", f"round over — {win_team} wins{score}",
                     win_team=win_team, bomb=rnd.get("bomb"), dmg=dmg)
            state["round_dmg"] = None  # consumed — next round starts fresh
        state["round_phase"] = phase

        # ---- bomb ----
        bomb = rnd.get("bomb")
        prev_bomb = state.get("bomb")
        if bomb != prev_bomb and bomb is not None:
            if bomb == "planted":
                emit("bomb_plant", "bomb planted")
            elif bomb == "defused":
                emit("bomb_defused", "bomb defused")
            elif bomb == "exploded":
                emit("bomb_exploded", "bomb exploded")
        state["bomb"] = bomb

    # ---- player-scoped events ----
    if own:
        state["team"] = player.get("team") or state.get("team")
        stats = player.get("match_stats") or {}

        rk = pstate.get("round_kills")
        prev_rk = state.get("round_kills") or 0
        if rk is not None:
            if rk > prev_rk:
                hs = (pstate.get("round_killhs") or 0) > (state.get("round_killhs") or 0)
                title = _MULTI.get(rk, "kill")
                if title == "kill" and rk > 5:
                    title = f"{rk} kills"
                if hs and rk < 2:
                    title = "headshot kill"
                # active weapon at the moment of the kill — best-effort, not
                # authoritative (CS2 doesn't send a weapon-per-kill field,
                # this just reads whatever's currently held)
                weapons = player.get("weapons") or {}
                active = next((w.get("name") for w in weapons.values() if w.get("state") == "active"), None)
                if active and active.startswith("weapon_"):
                    active = active[len("weapon_"):]
                emit("kill", title, kills_in_round=rk, hs=hs, weapon=active)
            state["round_kills"] = rk
            state["round_killhs"] = pstate.get("round_killhs")

        health = pstate.get("health")
        prev_health = state.get("health")
        if health is not None:
            if health == 0 and (prev_health or 0) > 0:
                emit("death", "died")
            state["health"] = health

        mvps = stats.get("mvps")
        if mvps is not None:
            if mvps > (state.get("mvps") if state.get("mvps") is not None else mvps):
                emit("mvp", "round MVP")
            state["mvps"] = mvps

        assists = stats.get("assists")
        if assists is not None:
            if assists > (state.get("assists") if state.get("assists") is not None else assists):
                emit("assist", "assist")
            state["assists"] = assists

    return events


# ---- plugin-host extensions (beyond the bare adapter contract) ----
PROCESS_NAMES = ["cs2.exe", "cs2"]
ASSETS_DIR = "assets"          # radar maps, served at /plugin-assets/cs2-pack/
MARKER_STYLE = {
    "mvp": {"icon": "⭐", "color": "#ffd15c"},
    "bomb_plant": {"icon": "💣", "color": "#ff9a5c", "label": "bomb planted"},
    "bomb_defused": {"icon": "✂", "color": "#4ade80", "label": "defused"},
    "bomb_exploded": {"icon": "💥", "color": "#ff5c87", "label": "bomb"},
    "round_win": {"icon": "✔"}, "round_loss": {"icon": "✘"},
}
