using UnityEngine;
using UnityEngine.UI;
using TMPro;
using Ashwild.Player;
using Ashwild.UI;
namespace Ashwild.Inventory
{
///
/// The inventory window. It is a local UI panel (opening/closing is purely local — the item
/// data itself lives in the networked ), so it is driven by the
/// GameUIManager panel stack like any other panel. The controller object stays active so it can
/// build its grid when the local player spawns; Show/Hide only toggle the visual window.
///
public class InventoryUI : UIPanel
{
///
/// Marks this panel as the inventory: input is locked and the cursor shows, but the world
/// keeps running (unlike the pause menu).
///
public override PanelKind Kind => PanelKind.Inventory;
[Header("References")]
[SerializeField] private GameObject inventoryPanel;
[SerializeField] private Transform slotContainer;
[SerializeField] private GameObject slotPrefab;
[Header("Drag Ghost")]
[SerializeField] private GameObject ghostObject;
[SerializeField] private Image ghostIcon;
[SerializeField] private TextMeshProUGUI ghostQuantityText;
[Header("Context Menu")]
[SerializeField] private SlotContextMenu contextMenu;
[Header("Categories")]
[SerializeField] private InventoryCategoryManager categoryManager;
[Header("Hotbar")]
[SerializeField] private HotbarUI hotbarUI;
private SlotUI[] slotUIs;
private PlayerInventory inventory;
private bool bound;
///
/// Waits for the networked local player to spawn before building the inventory grid.
///
private void OnEnable()
{
PlayerEvents.LocalPlayerSpawned += HandleLocalPlayerSpawned;
}
///
/// Unsubscribes — mirrors OnEnable exactly.
///
private void OnDisable()
{
PlayerEvents.LocalPlayerSpawned -= HandleLocalPlayerSpawned;
}
private void Start()
{
// Setup shared ghost for all SlotUIs (hotbar + inventory) — does not need the player.
SlotUI.SetupGhost(ghostObject, ghostIcon, ghostQuantityText);
inventoryPanel.SetActive(false);
// The player may already exist (late UI init); otherwise we wait for the spawn event.
if (PlayerInventory.Instance != null)
BuildInventory();
}
///
/// Builds the grid as soon as the local player has spawned on the network.
///
private void HandleLocalPlayerSpawned() => BuildInventory();
///
/// Creates the non-hotbar slot UIs from the local player's inventory; runs once.
///
private void BuildInventory()
{
if (bound) return;
inventory = PlayerInventory.Instance;
if (inventory == null) return;
bound = true;
// Only create slots for non-hotbar indices (hotbarSize to inventorySize-1)
int nonHotbarCount = inventory.InventorySize - inventory.HotbarSize;
slotUIs = new SlotUI[nonHotbarCount];
for (int i = 0; i < nonHotbarCount; i++)
{
int slotIndex = inventory.HotbarSize + i;
GameObject slotGO = Instantiate(slotPrefab, slotContainer);
SlotUI slotUI = slotGO.GetComponent();
slotUI.Initialize(slotIndex, OnSwapRequested, OnSlotClicked);
slotUIs[i] = slotUI;
}
inventory.onSlotChanged.AddListener(RefreshSlot);
RefreshAll();
}
private void OnDestroy()
{
if (inventory != null)
inventory.onSlotChanged.RemoveListener(RefreshSlot);
}
///
/// Opens the inventory window (called by the GameUIManager panel stack).
///
public override void Show()
{
inventoryPanel.SetActive(true);
RefreshAll();
if (categoryManager != null)
categoryManager.Open();
if (hotbarUI != null)
hotbarUI.OnInventoryOpen();
}
///
/// Closes the inventory window and tears down its transient UI (ghost, context menu).
///
public override void Hide()
{
if (ghostObject != null)
ghostObject.SetActive(false);
if (contextMenu != null)
contextMenu.Hide();
if (categoryManager != null)
categoryManager.Close();
if (hotbarUI != null)
hotbarUI.OnInventoryClose();
inventoryPanel.SetActive(false);
}
///
/// Instant hide used when the manager initializes panels — just parks the window closed.
///
public override void HideInstant()
{
if (ghostObject != null)
ghostObject.SetActive(false);
inventoryPanel.SetActive(false);
}
// Called by any SlotUI (inventory or hotbar) when a drag-drop completes
public static void OnSwapRequested(int fromIndex, int toIndex)
{
PlayerInventory inv = PlayerInventory.Instance;
InventorySlot fromSlot = inv.GetSlot(fromIndex);
InventorySlot toSlot = inv.GetSlot(toIndex);
// Same stackable item: merge
if (!fromSlot.IsEmpty && !toSlot.IsEmpty
&& fromSlot.ItemData == toSlot.ItemData
&& toSlot.CanAccept(fromSlot.ItemData))
{
int leftover = toSlot.AddQuantity(fromSlot.Quantity);
if (leftover <= 0)
fromSlot.Clear();
else
fromSlot.Set(fromSlot.ItemData, leftover);
inv.onSlotChanged?.Invoke(fromIndex);
inv.onSlotChanged?.Invoke(toIndex);
}
else
{
// Swap
inv.SwapSlots(fromIndex, toIndex);
}
}
private void OnSlotClicked(int index, bool rightClick)
{
if (rightClick)
{
if (contextMenu != null)
contextMenu.Show(index);
else
inventory.UseItem(index);
}
}
private void RefreshSlot(int index)
{
if (slotUIs == null) return;
int localIndex = index - inventory.HotbarSize;
if (localIndex >= 0 && localIndex < slotUIs.Length)
slotUIs[localIndex].UpdateVisual(inventory.GetSlot(index));
}
private void RefreshAll()
{
if (slotUIs == null) return;
for (int i = 0; i < slotUIs.Length; i++)
{
int slotIndex = inventory.HotbarSize + i;
slotUIs[i].UpdateVisual(inventory.GetSlot(slotIndex));
}
}
}
}