## BuyMenu — Client-side buy menu UI for weapon and equipment purchases.
##
## Displays a grid of purchasable weapons with name, cost, and key stats.
## Highlights weapons the player can afford, shows current money at the top,
## and supports both keyboard (numeric keys) and mouse-click selection.
##
## Opens during the PREP (buy) phase when the player is inside a buy zone.
## Automatically closes when the buy phase ends or the player leaves the zone.
##
## Usage (add as child of the player's UI root or HUD):
##   var buy_menu = preload("res://client/characters/weapon/buy_menu.gd").new()
##   add_child(buy_menu)
##
##   # Show it
##   buy_menu.open()
##   buy_menu.populate(affordable_weapons_list, current_money)
##
## Signals:
##   buy_menu_opened()
##   buy_menu_closed()
##   purchase_completed(weapon_id)
##   purchase_failed(reason: String)
##
class_name BuyMenu
extends Control

# ---------------------------------------------------------------------------
# Signals
# ---------------------------------------------------------------------------
## Emitted when the buy menu opens.
signal buy_menu_opened()

## Emitted when the buy menu closes.
signal buy_menu_closed()

## Emitted when a purchase is confirmed by the server.
signal purchase_completed(weapon_id: String)

## Emitted when a purchase is denied by the server.
signal purchase_failed(reason: String)

# ---------------------------------------------------------------------------
# Exports — Layout
# ---------------------------------------------------------------------------
## Background panel colour.
@export var panel_color: Color = Color(0.1, 0.1, 0.15, 0.92)

## Accent colour for headers and affordable items.
@export var accent_color: Color = Color(0.2, 0.7, 0.3, 1.0)

## Colour for items the player cannot afford.
@export var unaffordable_color: Color = Color(0.5, 0.5, 0.5, 1.0)

## Text colour.
@export var text_color: Color = Color(0.95, 0.95, 0.95, 1.0)

## Key binding for opening the buy menu (default: B).
@export var open_key: Key = KEY_B

# ---------------------------------------------------------------------------
# Internal state
# ---------------------------------------------------------------------------
## Reference to the BuyMenuHandler on the server (for sending RPCs).
## Injected by the UI manager or found at runtime.
var buy_menu_handler_path: NodePath = NodePath()

var _is_open: bool = false
var _weapon_buttons: Array[Button] = []
var _populated: bool = false

# ---------------------------------------------------------------------------
# UI Nodes (built in _build_ui)
# ---------------------------------------------------------------------------
var _panel: Panel = null
var _money_label: Label = null
var _title_label: Label = null
var _weapon_container: Container = null
var _close_button: Button = null
var _status_label: Label = null

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

func _ready() -> void:
    _build_ui()
    visible = false

    # Listen for server purchase confirmations / denials
    # The BuyMenuHandler sends RPCs that land on this node.
    # We connect to them via the multiplayer API — they're named to match
    # the RPC names defined in BuyMenuHandler.

    # Connect input for toggle key
    set_process_input(true)

func _input(event: InputEvent) -> void:
    if event.is_action_pressed("buy_menu") or (
        event is InputEventKey and
        event.keycode == open_key and
        event.pressed and
        not event.echo
    ):
        if _is_open:
            close()
        else:
            open()
        accept_event()

# ---------------------------------------------------------------------------
# UI Construction
# ---------------------------------------------------------------------------

func _build_ui() -> void:
    # Main panel — centred, sized to fit content
    _panel = Panel.new()
    _panel.anchor_left = 0.15
    _panel.anchor_right = 0.85
    _panel.anchor_top = 0.1
    _panel.anchor_bottom = 0.9
    _panel.mouse_filter = Control.MOUSE_FILTER_PASS
    add_child(_panel)

    # Panel background style
    var style := StyleBoxFlat.new()
    style.bg_color = panel_color
    style.corner_radius_top_left = 8
    style.corner_radius_top_right = 8
    style.corner_radius_bottom_left = 8
    style.corner_radius_bottom_right = 8
    _panel.add_theme_stylebox_override("panel", style)

    # Vertical layout inside the panel
    var vbox := VBoxContainer.new()
    vbox.anchor_left = 0.0
    vbox.anchor_right = 1.0
    vbox.anchor_top = 0.0
    vbox.anchor_bottom = 1.0
    vbox.add_theme_constant_override("separation", 12)
    _panel.add_child(vbox)

    # ── Title ──
    _title_label = Label.new()
    _title_label.text = "BUY MENU"
    _title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
    _title_label.add_theme_font_size_override("font_size", 24)
    _title_label.add_theme_color_override("font_color", accent_color)
    vbox.add_child(_title_label)

    # ── Money display ──
    _money_label = Label.new()
    _money_label.text = "$0"
    _money_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
    _money_label.add_theme_font_size_override("font_size", 20)
    _money_label.add_theme_color_override("font_color", text_color)
    vbox.add_child(_money_label)

    # ── Weapon grid ──
    _weapon_container = GridContainer.new()
    _weapon_container.columns = 2
    _weapon_container.add_theme_constant_override("h_separation", 16)
    _weapon_container.add_theme_constant_override("v_separation", 8)
    _weapon_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
    _weapon_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
    vbox.add_child(_weapon_container)

    # ── Status / feedback line ──
    _status_label = Label.new()
    _status_label.text = ""
    _status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
    _status_label.add_theme_color_override("font_color", Color(1.0, 0.8, 0.2, 1.0))
    _status_label.add_theme_font_size_override("font_size", 14)
    vbox.add_child(_status_label)

    # ── Close hint ──
    var close_hint := Label.new()
    close_hint.text = "Press B or ESC to close  |  Click or press 1-4 to buy"
    close_hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
    close_hint.add_theme_font_size_override("font_size", 11)
    close_hint.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6, 1.0))
    vbox.add_child(close_hint)

# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

## Open and populate the buy menu.
func open() -> void:
    if _is_open:
        return

    _is_open = true
    visible = true
    _populated = false
    _clear_weapons()
    _status_label.text = ""

    # Request affordable weapons from the server via an RPC call
    # (The server pushes the data back via RPC response.)
    _request_menu_data()

    buy_menu_opened.emit()

## Close the buy menu.
func close() -> void:
    if not _is_open:
        return
    _is_open = false
    visible = false
    buy_menu_closed.emit()

## Returns true if the menu is currently open.
func is_open() -> bool:
    return _is_open

## Update the displayed money amount.
func set_money(amount: int) -> void:
    _money_label.text = "$%d" % amount

## Populate the weapon grid with purchasable items.
## `weapons` is an Array[Dictionary] with keys:
##   weapon_id, display_name, cost, damage, fire_rate, mag_size
## `current_money` is the player's balance.
func populate(weapons: Array[Dictionary], current_money: int) -> void:
    _clear_weapons()
    _populated = false
    set_money(current_money)

    var index: int = 1
    for w in weapons:
        var affordable: bool = (current_money >= w.get("cost", 0))
        _add_weapon_button(index, w, affordable)
        index += 1

    _populated = true

## Show a temporary status message in the menu.
func show_status(message: String, duration: float = 3.0) -> void:
    _status_label.text = message
    if duration > 0:
        get_tree().create_timer(duration).timeout.connect(func():
            if is_instance_valid(_status_label) and _status_label.text == message:
                _status_label.text = ""
        )

# ---------------------------------------------------------------------------
# Internal: weapon button creation
# ---------------------------------------------------------------------------

func _add_weapon_button(index: int, data: Dictionary, affordable: bool) -> void:
    var weapon_id: String = data.get("weapon_id", "")
    var display_name: String = data.get("display_name", weapon_id)
    var cost: int = data.get("cost", 0)
    var damage: float = data.get("damage", 0.0)
    var fire_rate: float = data.get("fire_rate", 0.0)
    var mag_size: int = data.get("mag_size", 0)

    # Build the weapon card as a styled Button
    var btn := Button.new()
    btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
    btn.size_flags_vertical = Control.SIZE_SHRINK_CENTER
    btn.custom_minimum_size = Vector2(240, 80)

    # Text: name + cost on first line, stats on second
    var stats_line: String = "%.0f dmg | %.1f rps | %d rds" % [damage, fire_rate, mag_size]
    btn.text = "[%d] %s — $%d\n%s" % [index, display_name, cost, stats_line]
    btn.flat = false
    btn.tooltip_text = "%s — $%d\nDamage: %.0f\nFire Rate: %.1f Hz\nMagazine: %d rounds" % [
        display_name, cost, damage, fire_rate, mag_size
    ]

    # Style
    var fg_color: Color = accent_color if affordable else unaffordable_color
    btn.add_theme_color_override("font_color", fg_color)
    btn.add_theme_color_override("font_hover_color", text_color)
    btn.add_theme_color_override("font_pressed_color", Color.WHITE)
    btn.add_theme_font_size_override("font_size", 14)

    if not affordable:
        btn.disabled = true

    btn.pressed.connect(_on_weapon_button_pressed.bind(weapon_id, cost, btn))
    _weapon_container.add_child(btn)
    _weapon_buttons.append(btn)

func _clear_weapons() -> void:
    for child in _weapon_buttons:
        if is_instance_valid(child):
            child.queue_free()
    _weapon_buttons.clear()
    _populated = false

# ---------------------------------------------------------------------------
# Internal: purchase flow
# ---------------------------------------------------------------------------

func _on_weapon_button_pressed(weapon_id: String, cost: int, _button: Button) -> void:
    _request_purchase(weapon_id)

## Send a purchase request RPC to the server.
func _request_purchase(weapon_id: String) -> void:
    _status_label.text = "Purchasing %s..." % weapon_id

    # Find the BuyMenuHandler and call its buy_request RPC.
    # The handler is typically a child of GameServer.
    if multiplayer.is_server():
        # Listen server / standalone — call directly
        _call_buy_handler(weapon_id)
    else:
        # Dedicated client — send RPC
        _call_buy_handler_rpc(weapon_id)

func _call_buy_handler(_weapon_id: String) -> void:
    # On a listen server, the handler is local
    var handler: BuyMenuHandler = _find_buy_handler()
    if handler:
        handler.buy_request.rpc(_weapon_id)

func _call_buy_handler_rpc(weapon_id: String) -> void:
    # Find the handler path and send the RPC
    var handler_path: NodePath = buy_menu_handler_path
    if handler_path.is_empty():
        # Try to find it dynamically
        handler_path = "/root/GameServer/BuyMenuHandler"
    var node: Node = get_node_or_null(handler_path) if not handler_path.is_empty() else null
    if node and node.has_method(&"buy_request"):
        node.buy_request.rpc(weapon_id)
    else:
        # Fallback: call directly on the handler node path
        rpc_id(1, "_remote_buy_request", weapon_id)

## Fallback: send buy request to server's handler.
@rpc("any_peer", "call_remote", "reliable")
func _remote_buy_request(weapon_id: String) -> void:
    if not multiplayer.is_server():
        return
    var handler: BuyMenuHandler = _find_buy_handler()
    if handler:
        var sender: int = multiplayer.get_remote_sender_id()
        handler.call_deferred("_process_buy_request", sender, weapon_id)

func _find_buy_handler() -> BuyMenuHandler:
    # Try common locations
    var game_server: Node = get_node_or_null("/root/GameServer")
    if game_server:
        for child in game_server.get_children():
            if child is BuyMenuHandler:
                return child
    return null

# ---------------------------------------------------------------------------
# RPC handlers — called by BuyMenuHandler (server → client)
# ---------------------------------------------------------------------------

## Called by the server when a purchase succeeds.
@rpc("authority", "call_remote", "reliable")
func _on_buy_confirmed(weapon_id: String, cost: int) -> void:
    show_status("Purchased %s for $%d" % [weapon_id, cost], 2.0)
    purchase_completed.emit(weapon_id)
    # Close after a brief delay so the player sees the confirmation
    get_tree().create_timer(0.6).timeout.connect(close)

## Called by the server when a purchase is denied.
@rpc("authority", "call_remote", "reliable")
func _on_buy_denied(reason: String) -> void:
    show_status("Purchase failed: %s" % reason, 3.0)
    purchase_failed.emit(reason)

# ---------------------------------------------------------------------------
# Request affordable weapon data from the server
# ---------------------------------------------------------------------------

func _request_menu_data() -> void:
    # For now, the client populates from the weapon definitions directly,
    # filtering by what the server says is affordable.
    # In a more advanced version, the server sends the curated list.
    #
    # Since we don't know the player's money on the client, we send an
    # RPC to the server to get the affordable list. But for simplicity,
    # we populate all weapons and let the server deny if unaffordable.
    #
    # The server pushes the affordable list back via get_affordable_weapons().
    # For now, just show all weapons — the server will reject unaffordable ones.

    # Try to fetch from server
    if multiplayer.is_server():
        _populate_from_local(player_id())
    else:
        _request_affordable_weapons.rpc_id(1)

## Get the player's peer ID.
func player_id() -> int:
    return multiplayer.get_unique_id()

## Client → Server: request affordable weapon list.
@rpc("any_peer", "call_remote", "reliable")
func _request_affordable_weapons() -> void:
    if not multiplayer.is_server():
        return
    var sender: int = multiplayer.get_remote_sender_id()
    var handler: BuyMenuHandler = _find_buy_handler()
    if handler:
        var weapons: Array[Dictionary] = handler.get_affordable_weapons(sender)
        var money: int = handler.get_player_money(sender)
        _send_menu_data.rpc_id(sender, weapons, money)

## Server → Client: send affordable weapon list and money.
@rpc("authority", "call_remote", "reliable")
func _send_menu_data(weapons: Array[Dictionary], current_money: int) -> void:
    populate(weapons, current_money)

func _populate_from_local(pid: int) -> void:
    var handler: BuyMenuHandler = _find_buy_handler()
    if handler:
        var weapons: Array[Dictionary] = handler.get_affordable_weapons(pid)
        var money: int = handler.get_player_money(pid)
        populate(weapons, money)

# ---------------------------------------------------------------------------
# Input helpers
# ---------------------------------------------------------------------------

func _unhandled_key_input(event: InputEventKey) -> void:
    if not _is_open or not _populated:
        return

    # Numeric keys 1-4 to buy
    var key: Key = event.keycode
    var index: int = -1
    match key:
        KEY_1, KEY_KP_1: index = 0
        KEY_2, KEY_KP_2: index = 1
        KEY_3, KEY_KP_3: index = 2
        KEY_4, KEY_KP_4: index = 3

    if index >= 0 and index < _weapon_buttons.size():
        var btn: Button = _weapon_buttons[index]
        if btn and not btn.disabled and event.pressed:
            btn.emit_signal("pressed")
            accept_event()

    # ESC to close
    if event.keycode == KEY_ESCAPE and event.pressed:
        close()
        accept_event()
