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


#include "CraftingComponent.h"

#include "CraftingRecipeBook.h"
#include "InventoryComponent.h"
#include "ItemDataAsset.h"

UCraftingComponent::UCraftingComponent()
{
	PrimaryComponentTick.bCanEverTick = false;

	// Necessaire pour que InitializeComponent() soit appele.
	bWantsInitializeComponent = true;

	// Aucune propriete repliquee : le composant ne porte que StationRefCounts,
	// tenu a jour des deux cotes par le controller. C'est uniquement le routage
	// de Server_Craft qui l'exige -- un composant non replique voit ses RPC
	// serveur silencieusement jetees.
	SetIsReplicatedByDefault(true);
}

void UCraftingComponent::InitializeComponent()
{
	Super::InitializeComponent();

	// Les mains sont toujours disponibles, et rien ne peut les retirer.
	StationRefCounts.Add(ECraftingStation::Hands, 1);

	if (const AActor* Owner = GetOwner())
	{
		Inventory = Owner->FindComponentByClass<UInventoryComponent>();
	}

	if (!Inventory)
	{
		UE_LOG(LogTemp, Warning, TEXT("UCraftingComponent : aucun UInventoryComponent sur %s, aucune fabrication ne sera possible."),
			*GetNameSafe(GetOwner()));
	}

	if (!RecipeBook)
	{
		UE_LOG(LogTemp, Warning, TEXT("UCraftingComponent : RecipeBook n'est pas assigne sur %s, la grille de craft restera vide."),
			*GetNameSafe(GetOwner()));
	}
}

bool UCraftingComponent::HasStation(ECraftingStation Station) const
{
	const int32* Count = StationRefCounts.Find(Station);
	return Count && *Count > 0;
}

void UCraftingComponent::AddAvailableStation(ECraftingStation Station)
{
	if (Station == ECraftingStation::Count)
	{
		return;
	}

	int32& Count = StationRefCounts.FindOrAdd(Station);
	++Count;

	// On ne diffuse que sur la transition 0 -> 1. Entrer dans le rayon d'un
	// deuxieme etabli ne change rien de visible, inutile de reconstruire la
	// grille de recettes pour ca.
	if (Count == 1)
	{
		OnAvailableStationsChanged.Broadcast();
	}
}

void UCraftingComponent::RemoveAvailableStation(ECraftingStation Station)
{
	// Retirer les mains laisserait le joueur incapable de fabriquer quoi que ce
	// soit, sans qu'aucun message ne l'explique.
	if (Station == ECraftingStation::Hands)
	{
		return;
	}

	int32* Count = StationRefCounts.Find(Station);
	if (!Count || *Count <= 0)
	{
		return;
	}

	--(*Count);

	if (*Count <= 0)
	{
		StationRefCounts.Remove(Station);
		OnAvailableStationsChanged.Broadcast();
	}
}

void UCraftingComponent::GatherRecipes(TArray<UCraftingRecipeDataAsset*>& OutRecipes, ECraftingStation StationFilter) const
{
	OutRecipes.Reset();

	if (!RecipeBook)
	{
		return;
	}

	const TArray<TObjectPtr<UCraftingRecipeDataAsset>>& AllRecipes = RecipeBook->GetRecipes();
	OutRecipes.Reserve(AllRecipes.Num());

	for (UCraftingRecipeDataAsset* Recipe : AllRecipes)
	{
		// Une entree vide dans le livre est signalee par sa validation d'asset.
		// Ici on se contente de la sauter.
		if (!Recipe)
		{
			continue;
		}

		if (!HasStation(Recipe->RequiredStation))
		{
			continue;
		}

		if (StationFilter != ECraftingStation::Count && Recipe->RequiredStation != StationFilter)
		{
			continue;
		}

		OutRecipes.Add(Recipe);
	}
}

bool UCraftingComponent::CanCraft(const UCraftingRecipeDataAsset* Recipe, ECraftFailureReason& OutReason) const
{
	OutReason = ECraftFailureReason::None;

	if (!RecipeBook)
	{
		OutReason = ECraftFailureReason::NoRecipeBook;
		return false;
	}

	if (!Recipe || !Recipe->ResultItem)
	{
		OutReason = ECraftFailureReason::InvalidRecipe;
		return false;
	}

	if (!Inventory)
	{
		OutReason = ECraftFailureReason::InvalidRecipe;
		return false;
	}

	if (!HasStation(Recipe->RequiredStation))
	{
		OutReason = ECraftFailureReason::StationUnavailable;
		return false;
	}

	for (const FCraftIngredient& Ingredient : Recipe->Ingredients)
	{
		// Un ingredient nul est refuse par la validation de l'asset. S'il passe
		// quand meme, on ne fabrique pas : mieux vaut un bouton grise qu'une
		// recette qui coute moins cher que ce qu'elle affiche.
		if (!Ingredient.Item)
		{
			OutReason = ECraftFailureReason::InvalidRecipe;
			return false;
		}

		if (Inventory->GetItemCount(Ingredient.Item) < Ingredient.Quantity)
		{
			OutReason = ECraftFailureReason::MissingIngredients;
			return false;
		}
	}

	return true;
}

void UCraftingComponent::GatherIngredientStatus(const UCraftingRecipeDataAsset* Recipe, TArray<FCraftIngredientStatus>& OutStatus) const
{
	OutStatus.Reset();

	if (!Recipe)
	{
		return;
	}

	OutStatus.Reserve(Recipe->Ingredients.Num());

	for (const FCraftIngredient& Ingredient : Recipe->Ingredients)
	{
		if (!Ingredient.Item)
		{
			continue;
		}

		FCraftIngredientStatus& Status = OutStatus.AddDefaulted_GetRef();
		Status.Item = Ingredient.Item;
		Status.RequiredQuantity = Ingredient.Quantity;
		Status.OwnedQuantity = Inventory ? Inventory->GetItemCount(Ingredient.Item) : 0;
	}
}

void UCraftingComponent::Server_Craft_Implementation(UCraftingRecipeDataAsset* Recipe)
{
	Craft(Recipe);
}

bool UCraftingComponent::Craft(UCraftingRecipeDataAsset* Recipe)
{
	// Fabriquer consomme et ajoute dans l'inventaire, devenu autoritaire.
	// Le client demande, le serveur decide -- et il refait CanCraft de son cote
	// avec SA liste de postes accessibles : sans ca, un client desynchronise
	// fabriquerait une recette d'etabli au milieu de la foret.
	const AActor* Owner = GetOwner();
	if (!Owner || !Owner->HasAuthority())
	{
		Server_Craft(Recipe);

		// "Demande partie", pas "objet fabrique". Le resultat arrivera par la
		// replication de l'inventaire, et OnCraftSucceeded n'est diffuse que
		// sur le serveur -- une animation de reussite devra passer par une RPC
		// Client_ le jour ou il y en aura une.
		return true;
	}

	ECraftFailureReason Reason = ECraftFailureReason::None;
	if (!CanCraft(Recipe, Reason))
	{
		return false;
	}

	// CanCraft a deja verifie que tout est present : aucun RemoveItem ne peut
	// echouer a partir d'ici, donc pas de consommation a moitie faite.
	//
	// Chaque retrait diffuse OnInventoryChanged, donc l'interface se rafraichit
	// une fois par ingredient. C'est du gaspillage assume : ca reste quelques
	// dizaines de SetSlot sur un clic, et grouper les diffusions demanderait
	// une API de lot dans l'inventaire pour un gain invisible.
	for (const FCraftIngredient& Ingredient : Recipe->Ingredients)
	{
		Inventory->RemoveItem(Ingredient.Item, Ingredient.Quantity);
	}

	// -1 : l'objet sort neuf de la fabrication, avec toutes ses charges.
	const int32 Leftover = Inventory->AddItem(Recipe->ResultItem, Recipe->ResultQuantity, -1);

	if (Leftover > 0)
	{
		if (OnCraftOverflow.IsBound())
		{
			OnCraftOverflow.Broadcast(Recipe->ResultItem, Leftover, -1);
		}
		else
		{
			// Personne pour poser l'objet au sol : il est perdu. C'est une erreur
			// de branchement, pas une situation de jeu -- d'ou le niveau Error.
			UE_LOG(LogTemp, Error,
				TEXT("UCraftingComponent : %d x %s n'ont pas pu entrer dans l'inventaire et personne n'ecoute OnCraftOverflow. Objets perdus."),
				Leftover, *GetNameSafe(Recipe->ResultItem));
		}
	}

	OnCraftSucceeded.Broadcast(Recipe);
	return true;
}
