// Fill out your copyright notice in the Description page of Project Settings.


#include "InventoryWidget.h"

#include "Components/UniformGridPanel.h"
#include "Components/UniformGridSlot.h"
#include "FpsPlayer.h"
#include "GameFramework/PlayerController.h"
#include "InventoryComponent.h"
#include "InventoryDragDropOperation.h"
#include "InventorySlotWidget.h"

void UInventoryWidget::NativeConstruct()
{
	Super::NativeConstruct();

	BindToOwningPawn();
}

void UInventoryWidget::BindToOwningPawn()
{
	UInventoryComponent* NewInventory = ResolveInventory();
	if (BoundInventory.Get() == NewInventory)
	{
		return;
	}

	if (BoundInventory.IsValid())
	{
		BoundInventory->OnInventoryChanged.RemoveDynamic(this, &UInventoryWidget::HandleInventoryChanged);
	}

	BoundInventory = NewInventory;

	if (BoundInventory.IsValid())
	{
		BoundInventory->OnInventoryChanged.AddDynamic(this, &UInventoryWidget::HandleInventoryChanged);
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("UInventoryWidget : aucun UInventoryComponent trouve sur le pawn possede."));
	}

	// Les cases gardent l'ancien composant en memoire : on force leur
	// recreation en vidant la grille.
	for (UInventorySlotWidget* SlotWidget : SlotWidgets)
	{
		if (SlotWidget)
		{
			SlotWidget->RemoveFromParent();
		}
	}
	SlotWidgets.Reset();

	Refresh();
}

void UInventoryWidget::NativeDestruct()
{
	if (BoundInventory.IsValid())
	{
		BoundInventory->OnInventoryChanged.RemoveDynamic(this, &UInventoryWidget::HandleInventoryChanged);
	}
	BoundInventory.Reset();

	Super::NativeDestruct();
}

UInventoryComponent* UInventoryWidget::ResolveInventory() const
{
	const APlayerController* OwningController = GetOwningPlayer();
	AFpsPlayer* Player = OwningController ? Cast<AFpsPlayer>(OwningController->GetPawn()) : nullptr;
	return Player ? Player->GetInventoryComponent() : nullptr;
}

void UInventoryWidget::HandleInventoryChanged()
{
	Refresh();
}

void UInventoryWidget::NotifySlotHovered(int32 Index, bool bHovered)
{
	if (bHovered)
	{
		HoveredSlotIndex = Index;
	}
	else if (HoveredSlotIndex == Index)
	{
		// Le test d'egalite evite d'effacer le survol quand les evenements
		// arrivent dans l'ordre "entre dans B" puis "sort de A".
		HoveredSlotIndex = INDEX_NONE;
	}
}

bool UInventoryWidget::NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation)
{
	Super::NativeOnDrop(InGeometry, InDragDropEvent, InOperation);

	// On n'arrive ici que si AUCUNE case n'a traite le depot : les widgets
	// enfants sont consultes en premier et arretent la propagation.
	const UInventoryDragDropOperation* Payload = Cast<UInventoryDragDropOperation>(InOperation);
	if (!Payload || Payload->SourceSlotIndex == INDEX_NONE)
	{
		return true;
	}

	// Lache DANS la grille mais entre deux cases (l'espacement) : on absorbe.
	// Jeter un objet au sol parce que le joueur a rate une case de trois
	// pixels serait impardonnable dans un jeu de survie.
	if (SlotGrid && SlotGrid->GetCachedGeometry().IsUnderLocation(InDragDropEvent.GetScreenSpacePosition()))
	{
		return true;
	}

	// Lache hors de la grille : on jette au sol, comme dans Valheim ou Raft.
	if (const APlayerController* OwningController = GetOwningPlayer())
	{
		if (AFpsPlayer* Player = Cast<AFpsPlayer>(OwningController->GetPawn()))
		{
			Player->DropSlot(Payload->SourceSlotIndex, Payload->Quantity);
		}
	}

	return true;
}

void UInventoryWidget::Refresh()
{
	if (!SlotGrid)
	{
		UE_LOG(LogTemp, Warning, TEXT("UInventoryWidget::Refresh : SlotGrid est nul. Le widget nomme 'SlotGrid' manque dans le Blueprint."));
		return;
	}

	if (!BoundInventory.IsValid())
	{
		UE_LOG(LogTemp, Warning, TEXT("UInventoryWidget::Refresh : aucun inventaire lie."));
		return;
	}

	const TArray<FInventorySlot>& Slots = BoundInventory->GetSlots();

	// La grille commence APRES la barre rapide : les deux conteneurs sont
	// independants, un objet dans la barre n'apparait pas ici.
	const int32 Offset = BoundInventory->GetBackpackStartIndex();
	const int32 GridCount = FMath::Max(0, Slots.Num() - Offset);

	// Le nombre de slots peut changer en cours de partie (sac equipe).
	// On ne reconstruit que si la taille a bouge : recreer toutes les cases
	// a chaque ramassage serait du gaspillage pur.
	RebuildGrid(GridCount);

	for (int32 Index = 0; Index < SlotWidgets.Num(); ++Index)
	{
		const int32 Absolute = Offset + Index;
		if (SlotWidgets[Index] && Slots.IsValidIndex(Absolute))
		{
			SlotWidgets[Index]->SetSlot(Slots[Absolute]);
		}
	}
}

void UInventoryWidget::RebuildGrid(int32 DesiredCount)
{
	if (SlotWidgets.Num() == DesiredCount)
	{
		return;
	}

	if (!SlotWidgetClass)
	{
		UE_LOG(LogTemp, Warning, TEXT("UInventoryWidget : SlotWidgetClass n'est pas assignee, la grille restera vide."));
		return;
	}

	// Trop de cases : on retire le surplus par la fin.
	while (SlotWidgets.Num() > DesiredCount)
	{
		const int32 Last = SlotWidgets.Num() - 1;
		if (SlotWidgets[Last])
		{
			SlotWidgets[Last]->RemoveFromParent();
		}
		SlotWidgets.RemoveAt(Last);
	}

	// Pas assez : on en cree, en les placant en ligne / colonne.
	const int32 Columns = FMath::Max(1, ColumnCount);
	while (SlotWidgets.Num() < DesiredCount)
	{
		UInventorySlotWidget* NewSlot = CreateWidget<UInventorySlotWidget>(this, SlotWidgetClass);
		if (!NewSlot)
		{
			// Arrive typiquement quand le Widget Blueprint de case ne compile
			// plus, apres un changement de BindWidget cote C++ par exemple.
			UE_LOG(LogTemp, Error, TEXT("UInventoryWidget : impossible de creer une case a partir de %s. Recompile ce Widget Blueprint."),
				*GetNameSafe(SlotWidgetClass));
			break;
		}

		const int32 Index = SlotWidgets.Num();

		// On donne l'index ABSOLU dans le tableau, pas la position dans la
		// grille. C'est ce qui rend le glisser entre la barre et la grille
		// gratuit : les deux manipulent les memes index.
		const int32 Absolute = BoundInventory->GetBackpackStartIndex() + Index;
		NewSlot->InitSlot(BoundInventory.Get(), Absolute, this);
		SlotGrid->AddChildToUniformGrid(NewSlot, Index / Columns, Index % Columns);
		SlotWidgets.Add(NewSlot);
	}
}
