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


#include "InventorySubsystem.h"

#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "NakedDesire/Items/ItemDefinition.h"
#include "NakedDesire/Items/ItemInstance.h"
#include "NakedDesire/Phone/PhoneItemDefinition.h"
#include "NakedDesire/Phone/PhoneItemInstance.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
#include "NakedDesire/SaveGame/GlobalSaveGameData.h"
#include "NakedDesire/SaveGame/ItemSaveRecord.h"
#include "NakedDesire/SaveGame/SaveSubsystem.h"

const TArray<TObjectPtr<UItemInstance>>& UInventorySubsystem::GetWardrobeItems()
{
	EnsureHydrated();
	return WardrobeItems;
}

UPhoneItemInstance* UInventorySubsystem::GetEquippedPhone()
{
	EnsureHydrated();
	return EquippedPhone;
}

void UInventorySubsystem::AddToWardrobe(UItemInstance* Item)
{
	EnsureHydrated();
	StoreItem(Item);
	OnWardrobeChanged.Broadcast();
}

void UInventorySubsystem::RemoveFromWardrobe(UItemInstance* Item)
{
	EnsureHydrated();
	UnstoreItem(Item);
	OnWardrobeChanged.Broadcast();
}

void UInventorySubsystem::EquipFromWardrobe(UItemInstance* Item)
{
	EnsureHydrated();

	if (UPhoneItemInstance* Phone = Cast<UPhoneItemInstance>(Item))
	{
		EquipPhone(Phone);
		return;
	}

	UClothingItemInstance* Clothing = Cast<UClothingItemInstance>(Item);
	if (!Clothing || !Clothing->GetClothingItemDefinition())
		return;

	UClothingManager* ClothingManager = GetPlayerClothingManager();
	if (!ClothingManager)
		return;

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

	// Return the current occupant of the target slot to the wardrobe (never drop it to the world).
	if (UClothingItemInstance* Occupant = ClothingManager->GetSlotClothing(SlotType))
	{
		ClothingManager->RemoveClothing(SlotType);
		StoreItem(Occupant);
	}

	// Bodysuit exclusion (§6.5): vacate conflicting slots back into the wardrobe.
	for (const EClothingSlotType ExcludedSlot : UClothingManager::GetBodysuitExcludedSlots(SlotType))
	{
		if (UClothingItemInstance* Excluded = ClothingManager->GetSlotClothing(ExcludedSlot))
		{
			ClothingManager->RemoveClothing(ExcludedSlot);
			StoreItem(Excluded);
		}
	}

	UnstoreItem(Clothing);
	ClothingManager->EquipSlot(Clothing);

	OnWardrobeChanged.Broadcast();
}

void UInventorySubsystem::UnequipToWardrobe(UItemInstance* Item)
{
	EnsureHydrated();

	if (UPhoneItemInstance* Phone = Cast<UPhoneItemInstance>(Item))
	{
		UnequipPhone(Phone);
		return;
	}

	UClothingItemInstance* Clothing = Cast<UClothingItemInstance>(Item);
	if (!Clothing || !Clothing->GetClothingItemDefinition())
		return;

	UClothingManager* ClothingManager = GetPlayerClothingManager();
	if (!ClothingManager)
		return;

	UClothingItemInstance* Removed = ClothingManager->RemoveClothing(Clothing->GetClothingItemDefinition()->SlotType);
	if (!Removed)
		return;

	StoreItem(Removed);
	OnWardrobeChanged.Broadcast();
}

void UInventorySubsystem::EquipPhone(UPhoneItemInstance* Phone)
{
	if (!Phone || Phone == EquippedPhone)
		return;

	UGlobalSaveGameData* Save = GetSave();

	// Hot-swap: the previously equipped phone returns to the wardrobe (§9.9).
	if (EquippedPhone)
	{
		if (Save)
			Save->RemoveEquippedItem(EquippedPhone);
		StoreItem(EquippedPhone);
	}

	UnstoreItem(Phone);
	EquippedPhone = Phone;
	if (Save)
		Save->AddEquippedItem(Phone);

	OnPhoneChanged.Broadcast(EquippedPhone);
	OnWardrobeChanged.Broadcast();
}

void UInventorySubsystem::UnequipPhone(UPhoneItemInstance* Phone)
{
	if (!Phone || EquippedPhone != Phone)
		return;

	if (UGlobalSaveGameData* Save = GetSave())
		Save->RemoveEquippedItem(Phone);

	EquippedPhone = nullptr;
	StoreItem(Phone);

	OnPhoneChanged.Broadcast(nullptr);
	OnWardrobeChanged.Broadcast();
}

void UInventorySubsystem::EnsureHydrated()
{
	UGlobalSaveGameData* Save = GetSave();
	if (!Save)
		return;

	// Re-hydrate only when the underlying save object changes (fresh game / load game).
	if (HydratedSave.Get() == Save)
		return;

	HydratedSave = Save;
	WardrobeItems.Reset();

	for (const FItemSaveRecord& Record : Save->GetWardrobeItems())
	{
		if (UItemInstance* Instance = UItemInstance::CreateFromRecord(this, Record))
			WardrobeItems.Add(Instance);
	}

	// Only the phone is hydrated from the equipped bucket here; equipped clothing is owned and
	// hydrated by the per-character UClothingManager. Pre-check the definition type so we don't
	// mint throwaway clothing instances.
	EquippedPhone = nullptr;
	for (const FItemSaveRecord& Record : Save->GetEquippedItems())
	{
		const UItemDefinition* Definition = Record.Definition.LoadSynchronous();
		if (!Definition || !Definition->IsA<UPhoneItemDefinition>())
			continue;

		if (UPhoneItemInstance* Phone = Cast<UPhoneItemInstance>(UItemInstance::CreateFromRecord(this, Record)))
		{
			EquippedPhone = Phone;
			break;
		}
	}
}

void UInventorySubsystem::StoreItem(UItemInstance* Item)
{
	if (!Item || WardrobeItems.Contains(Item))
		return;

	WardrobeItems.Add(Item);
	if (UGlobalSaveGameData* Save = GetSave())
		Save->AddWardrobeItem(Item);
}

void UInventorySubsystem::UnstoreItem(UItemInstance* Item)
{
	if (!Item)
		return;

	WardrobeItems.Remove(Item);
	if (UGlobalSaveGameData* Save = GetSave())
		Save->RemoveWardrobeItem(Item);
}

UClothingManager* UInventorySubsystem::GetPlayerClothingManager() const
{
	const ANakedDesireCharacter* Player = Cast<ANakedDesireCharacter>(UGameplayStatics::GetPlayerCharacter(GetGameInstance(), 0));
	return Player ? Player->ClothingManager : nullptr;
}

UGlobalSaveGameData* UInventorySubsystem::GetSave() const
{
	USaveSubsystem* SaveSubsystem = GetGameInstance() ? GetGameInstance()->GetSubsystem<USaveSubsystem>() : nullptr;
	return SaveSubsystem ? SaveSubsystem->GetCurrentSave() : nullptr;
}