// © 2025 Naked People Team. All Rights Reserved.


#include "ClothingManager.h"
#include "ClothingItemDefinition.h"
#include "ClothingItemInstance.h"
#include "GameFramework/Character.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Interactables/ItemPickup.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
#include "NakedDesire/SaveGame/GlobalSaveGameData.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h"
#include "NakedDesire/SaveGame/SaveSubsystem.h"

UClothingManager::UClothingManager()
{
	PrimaryComponentTick.bCanEverTick = false;
}

void UClothingManager::BeginPlay()
{
	Super::BeginPlay();
	
	HydrateClothing();
}

void UClothingManager::SetClothingSlotItem(const EClothingSlotType ClothingSlotType, UClothingItemInstance* ClothingItemInstance)
{
	if (ClothingItemInstance)
		EquippedClothing.Add(ClothingSlotType, ClothingItemInstance);
	else
		EquippedClothing.Remove(ClothingSlotType);
}

TArray<UClothingItemInstance*> UClothingManager::GetEquippedClothing() const
{
	TArray<TObjectPtr<UClothingItemInstance>> Items;
	EquippedClothing.GenerateValueArray(Items);
	return Items;
}

UClothingItemInstance* UClothingManager::GetSlotClothing(const EClothingSlotType SlotType)
{
	if (EquippedClothing.Contains(SlotType))
		return EquippedClothing[SlotType];
	
	return nullptr;
}

bool UClothingManager::IsBodyPartExposed(const EBodyPart BodyPart)
{
	for (const auto& [Key, Value] : EquippedClothing)
	{
		if (Value->GetClothingItemDefinition()->HiddenBodyParts.Contains(BodyPart))
			return false;
	}
	
	return true;
}

float UClothingManager::GetEffectiveCoverage(const EBodyPart BodyPart)
{
	// GDD §6.3.2: effective coverage of a part = max() across garments that include it.
	// A garment that doesn't cover the part contributes nothing; nothing covering it = 0.
	float MaxCoverage = 0.0f;
	for (const auto& [Slot, Instance] : EquippedClothing)
	{
		if (!Instance)
			continue;

		const UClothingItemDefinition* Definition = Instance->GetClothingItemDefinition();
		if (!Definition)
			continue;

		for (const FBodyPartCoverage& Coverage : Definition->CoveredBodyParts)
		{
			if (Coverage.BodyPart == BodyPart)
				MaxCoverage = FMath::Max(MaxCoverage, Coverage.Coverage);
		}
	}

	return MaxCoverage;
}

float UClothingManager::GetBoobsSupport()
{
	const UClothingItemInstance* UnderwearTop = GetSlotClothing(EClothingSlotType::UnderwearTop);
	const UClothingItemInstance* Top = GetSlotClothing(EClothingSlotType::Top);
	const UClothingItemInstance* Bodysuit = GetSlotClothing(EClothingSlotType::Bodysuit);
	
	TArray<float> TotalSupports;
	
	if (UnderwearTop)
		TotalSupports.Push(UnderwearTop->GetClothingItemDefinition()->BoobsSupport);
	if (Top)
		TotalSupports.Push(Top->GetClothingItemDefinition()->BoobsSupport);
	if (Bodysuit)
		TotalSupports.Push(Bodysuit->GetClothingItemDefinition()->BoobsSupport);
	
	return FMath::Max(TotalSupports);
}

void UClothingManager::HydrateClothing()
{
	USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem<USaveSubsystem>();
	for (const FItemSaveRecord& ItemSaveRecord : SaveSubsystem->GetCurrentSave()->GetEquippedItems())
	{
		UClothingItemInstance* ClothingItemInstance = Cast<UClothingItemInstance>(UItemInstance::CreateFromRecord(this, ItemSaveRecord));
		if (!ClothingItemInstance)
			continue;

		PutOnClothing(ClothingItemInstance);
	}
}

float UClothingManager::GetHeelHeight()
{
	if (!EquippedClothing.Contains(EClothingSlotType::Footwear))
		return 0;
	
	const UClothingItemInstance* Footwear = EquippedClothing[EClothingSlotType::Footwear];
	
	return Footwear->GetClothingItemDefinition()->ShoesOffset;
}

void UClothingManager::SpawnClothingPickup(UClothingItemInstance* ItemInstance)
{
	if (!ItemPickupActor)
	{
		UE_LOG(LogTemp, Warning, TEXT("UClothingManager::SpawnClothingPickup ItemPickupActor is not set"));
		return;
	}

	AItemPickup* NewItemPickup = GetWorld()->SpawnActor<AItemPickup>(ItemPickupActor, GetOwner()->GetActorTransform());
	if (!NewItemPickup)
	{
		UE_LOG(LogTemp, Warning, TEXT("UClothingManager::SpawnClothingPickup NewItemPickup == nullptr"));
		return;
	}
	
	USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem<USaveSubsystem>();
	SaveSubsystem->GetCurrentSave()->AddWorldItem(ItemInstance, NewItemPickup->GetActorTransform());

	NewItemPickup->SetItem(ItemInstance);
}

void UClothingManager::PutOnClothing(UClothingItemInstance* ClothingItemInstance)
{
	if (!ClothingItemInstance || !ClothingItemInstance->GetClothingItemDefinition())
		return;

	// Pure apply-to-slot + notify. Exclusion / occupant handling lives with the caller, and the
	// EquippedItems save record is written by EquipSlot — hydration reuses this without re-adding.
	const EClothingSlotType ClothingSlotType = ClothingItemInstance->GetClothingItemDefinition()->SlotType;
	SetClothingSlotItem(ClothingSlotType, ClothingItemInstance);

	OnClothingEquip.Broadcast(ClothingItemInstance);
}

void UClothingManager::EquipSlot(UClothingItemInstance* ClothingItemInstance)
{
	if (!ClothingItemInstance)
		return;

	USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem<USaveSubsystem>();
	SaveSubsystem->GetCurrentSave()->AddEquippedItem(ClothingItemInstance);

	PutOnClothing(ClothingItemInstance);
}

void UClothingManager::TakeClothing(UClothingItemInstance* ClothingItemInstance)
{
	if (!ClothingItemInstance || !ClothingItemInstance->GetClothingItemDefinition())
		return;

	const EClothingSlotType SlotType = ClothingItemInstance->GetClothingItemDefinition()->SlotType;

	// World-pickup path: a displaced garment swaps back out to the world (no wardrobe nearby).
	if (IsClothingTypeOn(SlotType))
		DropClothing(SlotType);

	for (const EClothingSlotType ExcludedSlot : GetBodysuitExcludedSlots(SlotType))
	{
		if (IsClothingTypeOn(ExcludedSlot))
			DropClothing(ExcludedSlot);
	}

	EquipSlot(ClothingItemInstance);
}

TArray<EClothingSlotType> UClothingManager::GetBodysuitExcludedSlots(const EClothingSlotType SlotType)
{
	switch (SlotType)
	{
	case EClothingSlotType::Bodysuit:
		return { EClothingSlotType::Top, EClothingSlotType::Bottom,
			EClothingSlotType::UnderwearTop, EClothingSlotType::UnderwearBottom };
	case EClothingSlotType::Top:
	case EClothingSlotType::Bottom:
	case EClothingSlotType::UnderwearTop:
	case EClothingSlotType::UnderwearBottom:
		return { EClothingSlotType::Bodysuit };
	default:
		return {};
	}
}

UClothingItemInstance* UClothingManager::RemoveClothing(const EClothingSlotType ClothingSlotType)
{
	if (!EquippedClothing.Contains(ClothingSlotType))
		return nullptr;
	
	UClothingItemInstance* ExistingItem = EquippedClothing[ClothingSlotType];
	
	SetClothingSlotItem(ClothingSlotType, nullptr);

	OnClothingUnequip.Broadcast(ExistingItem);
	
	USaveSubsystem* SaveSubsystem = UGameplayStatics::GetGameInstance(GetWorld())->GetSubsystem<USaveSubsystem>();
	SaveSubsystem->GetCurrentSave()->RemoveEquippedItem(ExistingItem);
	
	return ExistingItem;
}

void UClothingManager::DropClothing(const EClothingSlotType ClothingType)
{
	UClothingItemInstance* ClothingItemInstance = RemoveClothing(ClothingType);
	if (!ClothingItemInstance)
		return;
	
	SpawnClothingPickup(ClothingItemInstance);

	OnClothingDropped.Broadcast(ClothingItemInstance);
}

bool UClothingManager::IsClothingTypeOn(const EClothingSlotType ClothingType)
{
	return EquippedClothing.Contains(ClothingType);
}
