UI/Inventory/InventorySlot.razor
@using Sandbox;
@using Sandbox.UI;
@namespace Sandbox
@inherits Panel

<root>
    @if ( !Input.UsingController )
    {
        <label class="index">@(Index + 1)</label>
    }

    @{
        var weapon = GetWeapon();
    }

    @if (weapon.IsValid())
    {
        <div class="icon" @ref="IconPanel"></div>
        <div class="name">@weapon.DisplayName</div>
    }
</root>

@code
{
    public PlayerInventory Inventory { get; set; }
    public bool Active { get; set; }
    public bool Hovered { get; set; }

    public int Index { get; set; }

    Panel IconPanel { get; set; }

    static InventorySlot _hoveredDragSlot;

    public override bool WantsDrag => true;

    protected override void OnDragStart( DragEvent e )
    {
        var weapon = GetWeapon();
        if ( !weapon.IsValid() ) return;

        var icon = weapon.InventoryIconOverride ?? weapon.DisplayIcon?.ResourcePath;

        DragHandler.StartDragging( new DragData
        {
            Source = this,
            Icon = icon,
            Title = weapon.DisplayName
        } );
    }

    protected override void OnDragEnd( DragEvent e )
    {
        var data = DragHandler.Current;
        DragHandler.StopDragging();
        base.OnDragEnd( e );

        // OnDrop on the target already handled the swap.
        if ( data?.Handled == true )
        {
            if ( data.Source is not null ) data.Source.UserData = null;
            return;
        }

        // Mouse is hovering over a different slot — OnDrop will fire and handle the swap.
        // Don't clear UserData yet so OnDrop can still retrieve it via e.Target.UserData.
        if ( _hoveredDragSlot is not null && _hoveredDragSlot != this )
            return;

        // No slot handled it — clean up.
        if ( data?.Source is not null ) data.Source.UserData = null;

        // If the mouse is still over this slot the user dragged back to the source — no-op.
        var mousePos = Mouse.Position * ScaleFromScreen;
        if ( Box.RectOuter.IsInside( mousePos ) ) return;

        var weapon = GetWeapon();
        if ( !Inventory.Drop( weapon ) && weapon.IsValid() )
            Inventory.Remove( weapon );
    }

    BaseCarryable GetWeapon()
    {
        if (!Inventory.IsValid()) return null;

        return Inventory.GetSlot(Index);
    }

    bool IsSpawnMenuOpen()
    {
        var host = Game.ActiveScene.Get<SpawnMenuHost>();
        return host?.Panel?.HasClass("open") ?? false;
    }

    public override void Tick()
    {
        base.Tick();

        var weapon = GetWeapon();
        var hasWeapon = weapon.IsValid();

        var spawnMenuOpen = IsSpawnMenuOpen();
        SetClass("droppable", spawnMenuOpen);

        SetClass("active", Hovered || Active);
        SetClass("empty", !hasWeapon);
        SetClass("selected", Active && hasWeapon);
        SetClass("hovered", Hovered && hasWeapon);
        SetClass("switchable", hasWeapon && weapon.CanSwitch());
        SetClass("no-tint", hasWeapon && weapon is SpawnerWeapon);

        if (hasWeapon && IconPanel is not null)
        {
            var iconOverride = weapon.InventoryIconOverride;
            if (!string.IsNullOrEmpty(iconOverride))
            {
                IconPanel.Style.SetBackgroundImage(iconOverride);
            }
            else
            {
                IconPanel.Style.SetBackgroundImage(weapon.DisplayIcon);
            }
        }
    }

    protected override void OnDragEnter(PanelEvent e)
    {
        AddClass("drag-hover");
        _hoveredDragSlot = this;
    }

    protected override void OnDragLeave(PanelEvent e)
    {
        RemoveClass("drag-hover");
        if ( _hoveredDragSlot == this ) _hoveredDragSlot = null;
    }

    protected override void OnDrop( PanelEvent e )
    {
        RemoveClass( "drag-hover" );
        _hoveredDragSlot = null;

        // Use DragHandler.Current first; fall back to UserData for the case where
        // OnDragEnd fired first and cleared Current (but preserved UserData).
        var data = DragHandler.Current ?? e.Target.UserData as DragData;

        // Clean up UserData now that the drop is being processed.
        if ( data?.Source is not null ) data.Source.UserData = null;

        if ( data is null ) return;

        // Hotbar-to-hotbar: move or swap the dragged slot with this slot.
        if ( data.Source is InventorySlot sourceSlot && sourceSlot.Inventory == Inventory )
        {
            data.Handled = true;
            Inventory.MoveSlot( sourceSlot.Index, Index );
            return;
        }

        GameManager.GiveSpawnerWeaponAt( data.Type, data.Path, Index, data.Data as string, data.Icon, data.Title );
    }

    protected override int BuildHash() => HashCode.Combine(GetWeapon(), Hovered, Active, Input.UsingController);
}