## BuyMenuHandler — Server-side buy request validation and processing.
##
## Receives buy_request RPCs from clients, validates against buy-zone,
## phase, and money constraints, then processes valid purchases.
##
## Each purchase deducts the weapon cost from the player's balance
## and calls WeaponServer.give_weapon() to grant the item.
##
## Usage (add as child of GameServer):
##   var bmh = BuyMenuHandler.new()
##   add_child(bmh)
##   bmh.initialise(economy_manager, weapon_server, team_manager)
##
## RPC protocol:
##   Client → Server: buy_request(weapon_id)
##   Server → Client: _on_buy_confirmed(weapon_id, cost)
##   Server → Client: _on_buy_denied(reason)
##
## Signals:
##   purchase_made(player_id, weapon_id, cost)
##   purchase_denied(player_id, weapon_id, reason)
##
class_name BuyMenuHandler
extends Node

const _td = preload("res://scripts/teams/team_data.gd")

# ---------------------------------------------------------------------------
# Signals
# ---------------------------------------------------------------------------
## Emitted when a purchase is successfully processed.
signal purchase_made(player_id: int, weapon_id: String, cost: int)

## Emitted when a purchase is denied (invalid weapon, insufficient funds,
## not in buy zone, etc.).
signal purchase_denied(player_id: int, weapon_id: String, reason: String)

# ---------------------------------------------------------------------------
# Dependencies (injected via initialise())
# ---------------------------------------------------------------------------
var economy_manager: EconomyManager = null
var weapon_server: WeaponServer = null
var team_manager: TeamManager = null

## Cached list of BuyZone nodes found in the current scene.
var _buy_zones: Array[BuyZone] = []

## If true, the buy-zone check is skipped (for admin / dev builds).
var bypass_zone_check: bool = false

# ---------------------------------------------------------------------------
# Lifecycle
# ---------------------------------------------------------------------------

## Initialise with required subsystem references.
## Must be called after the node is added to the scene tree.
func initialise(em: EconomyManager, ws: WeaponServer, tm: TeamManager) -> void:
    economy_manager = em
    weapon_server = ws
    team_manager = tm
    _refresh_buy_zones()

## Scan the scene tree for all BuyZone instances and cache them.
## Call this after every map load to pick up the new zone layout.
func _refresh_buy_zones() -> void:
    _buy_zones.clear()
    if not is_inside_tree():
        return
    var all_nodes: Array[Node] = get_tree().root.find_children("*", "BuyZone", true, false)
    for node in all_nodes:
        if node is BuyZone:
            _buy_zones.append(node)
    print("[BuyMenuHandler] Cached %d buy zone(s)" % _buy_zones.size())

# ---------------------------------------------------------------------------
# Buy-zone queries
# ---------------------------------------------------------------------------

## Returns true if the player is inside any buy zone that allows their team.
func is_player_in_buyzone(player_id: int) -> bool:
    if bypass_zone_check:
        return true
    if _buy_zones.is_empty():
        _refresh_buy_zones()
        if _buy_zones.is_empty():
            # No buy zones means players can buy anywhere (dev fallback)
            return true

    var player_team: int = TeamData.Team.SPECTATOR
    if team_manager:
        player_team = team_manager.get_player_team(player_id)

    for bz in _buy_zones:
        if bz.is_in_buyzone(player_id) and bz.is_team_allowed(player_team):
            return true
    return false

## Return the list of cached BuyZone nodes.
func get_buy_zones() -> Array[BuyZone]:
    return _buy_zones.duplicate()

# ---------------------------------------------------------------------------
# RPC — Client → Server: Buy request
# ---------------------------------------------------------------------------

## Client calls this RPC to attempt a weapon purchase.
## The server validates the request and responds with
## _on_buy_confirmed or _on_buy_denied.
@rpc("any_peer", "call_remote", "reliable")
func buy_request(weapon_id: String) -> void:
    if not multiplayer.is_server():
        return

    var player_id: int = multiplayer.get_remote_sender_id()
    _process_buy_request(player_id, weapon_id)

## Process a buy request (also callable directly from server-side code).
func _process_buy_request(player_id: int, weapon_id: String) -> void:
    # ── 1. Validate weapon exists ──
    var weapon_data: WeaponData = WeaponDefinitions.get_weapon(weapon_id)
    if weapon_data == null:
        purchase_denied.emit(player_id, weapon_id, "unknown_weapon")
        _send_buy_denied(player_id, "Unknown weapon: \"%s\"" % weapon_id)
        return

    # ── 2. Validate buy-zone membership ──
    if not is_player_in_buyzone(player_id):
        purchase_denied.emit(player_id, weapon_id, "not_in_buy_zone")
        _send_buy_denied(player_id, "You must be in a buy zone to purchase equipment")
        return

    # ── 3. Validate weapon is purchasable and affordable ──
    var cost: int = EconomyManager.get_weapon_cost(weapon_id)
    if cost < 0:
        purchase_denied.emit(player_id, weapon_id, "not_purchasable")
        _send_buy_denied(player_id, "This weapon cannot be purchased")
        return

    if economy_manager == null or not economy_manager.can_afford(player_id, cost):
        purchase_denied.emit(player_id, weapon_id, "insufficient_funds")
        var balance: int = economy_manager.get_money(player_id) if economy_manager else 0
        _send_buy_denied(player_id, "Insufficient funds — need $%d, have $%d" % [cost, balance])
        return

    # ── 4. Deduct money ──
    if not economy_manager.spend_money(player_id, cost):
        purchase_denied.emit(player_id, weapon_id, "transaction_failed")
        _send_buy_denied(player_id, "Transaction failed")
        return

    # ── 5. Grant the weapon ──
    if weapon_server:
        weapon_server.give_weapon(player_id, weapon_id)

    # ── 6. Confirm purchase ──
    purchase_made.emit(player_id, weapon_id, cost)
    _send_buy_confirmed(player_id, weapon_id, cost)
    print("[BuyMenuHandler] Player %d purchased '%s' for $%d" % [player_id, weapon_id, cost])

# ---------------------------------------------------------------------------
# RPC — Server → Client: Purchase result
# ---------------------------------------------------------------------------

## Send a purchase confirmation to the requesting client only.
func _send_buy_confirmed(player_id: int, weapon_id: String, cost: int) -> void:
    rpc_id(player_id, "_on_buy_confirmed", weapon_id, cost)

## Server-to-client RPC: purchase succeeded.
@rpc("authority", "call_remote", "reliable")
func _on_buy_confirmed(weapon_id: String, cost: int) -> void:
    # Handled on the client side by buy_menu.gd
    pass

## Send a purchase denial to the requesting client only.
func _send_buy_denied(player_id: int, reason: String) -> void:
    rpc_id(player_id, "_on_buy_denied", reason)

## Server-to-client RPC: purchase denied with a human-readable reason.
@rpc("authority", "call_remote", "reliable")
func _on_buy_denied(reason: String) -> void:
    # Handled on the client side by buy_menu.gd
    pass

# ---------------------------------------------------------------------------
# Public API — Query helpers (for UI / RCON)
# ---------------------------------------------------------------------------

## Return a list of weapon info dictionaries the player can afford.
## Each entry: {weapon_id, display_name, cost, damage, fire_rate, mag_size}
func get_affordable_weapons(player_id: int) -> Array[Dictionary]:
    var affordable: Array[Dictionary] = []
    var money: int = economy_manager.get_money(player_id) if economy_manager else 0

    for wid in EconomyManager.WEAPON_COSTS.keys():
        var cost: int = EconomyManager.WEAPON_COSTS[wid]
        if money >= cost:
            var data: WeaponData = WeaponDefinitions.get_weapon(wid)
            affordable.append({
                "weapon_id": wid,
                "display_name": data.display_name if data else wid,
                "cost": cost,
                "damage": data.damage if data else 0.0,
                "fire_rate": data.fire_rate if data else 0.0,
                "mag_size": data.mag_size if data else 0,
            })

    return affordable

## Return the player's current money balance.
func get_player_money(player_id: int) -> int:
    return economy_manager.get_money(player_id) if economy_manager else 0
