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


#include "MissionSubsystem.h"

#include "Commission.h"
#include "CommissionBoardConfig.h"
#include "CommissionTypes.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Global/NakedDesireGameInstance.h"
#include "NakedDesire/Global/TimeOfDaySubsystem.h"
#include "NakedDesire/Player/NakedDesireCharacter.h"
#include "NakedDesire/SaveGame/GlobalSaveGameData.h"
#include "NakedDesire/SaveGame/SaveSubsystem.h"

void UMissionSubsystem::OnWorldBeginPlay(UWorld& InWorld)
{
	Super::OnWorldBeginPlay(InWorld);

	if (UTimeOfDaySubsystem* Time = InWorld.GetSubsystem<UTimeOfDaySubsystem>())
		Time->OnDayChanged.AddUniqueDynamic(this, &UMissionSubsystem::HandleDayChanged);

	BuildBoard();
	RestoreFromSave();
	OnBoardChanged.Broadcast();
}

void UMissionSubsystem::Deinitialize()
{
	if (const UWorld* World = GetWorld())
	{
		if (UTimeOfDaySubsystem* Time = World->GetSubsystem<UTimeOfDaySubsystem>())
			Time->OnDayChanged.RemoveDynamic(this, &UMissionSubsystem::HandleDayChanged);
	}

	Super::Deinitialize();
}

void UMissionSubsystem::AcceptCommission(UCommission* Commission)
{
	if (!Commission || !OfferedCommissions.Contains(Commission))
		return;

	ANakedDesireCharacter* Player = GetPlayer();
	if (!Player)
		return;

	// Move to Accepted before arming: Accept() may complete synchronously (objectives already met),
	// and HandleCommissionCompleted expects the commission to be in AcceptedCommissions.
	OfferedCommissions.Remove(Commission);
	AcceptedCommissions.Add(Commission);
	Commission->OnCompleted.AddUObject(this, &UMissionSubsystem::HandleCommissionCompleted);

	Commission->Accept(Player);

	PersistState();
	OnBoardChanged.Broadcast();
}

void UMissionSubsystem::AbandonCommission(UCommission* Commission)
{
	if (!Commission || !AcceptedCommissions.Contains(Commission))
		return;

	Commission->OnCompleted.RemoveAll(this);
	Commission->Abandon();

	AcceptedCommissions.Remove(Commission);
	OfferedCommissions.Add(Commission);

	PersistState();
	OnBoardChanged.Broadcast();
}

void UMissionSubsystem::HandleDayChanged(int32 NewDay)
{
	// Day rolls expire anything still accepted, then a fresh board is offered. No RestoreFromSave here:
	// the persisted records are only meaningful for a load, and PersistState below clears them.
	ExpireAccepted();
	BuildBoard();
	PersistState();
	OnBoardChanged.Broadcast();
}

void UMissionSubsystem::BuildBoard()
{
	OfferedCommissions.Reset();

	const UCommissionBoardConfig* Config = GetBoardConfig();
	if (!Config)
		return;

	for (UCommission* Authored : Config->Commissions)
	{
		if (!Authored)
			continue;

		// Duplicate so each run gets fresh objective state and an outer in this world.
		UCommission* Runtime = DuplicateObject<UCommission>(Authored, this);
		OfferedCommissions.Add(Runtime);
	}
}

void UMissionSubsystem::RestoreFromSave()
{
	const UGlobalSaveGameData* Save = GetSave();
	if (!Save)
		return;

	ANakedDesireCharacter* Player = GetPlayer();

	for (const FCommissionSaveRecord& Record : Save->GetCommissionRecords())
	{
		UCommission** Found = OfferedCommissions.FindByPredicate(
			[&Record](const UCommission* C) { return C && C->CommissionId == Record.CommissionId; });
		if (!Found || !*Found)
			continue;

		UCommission* Commission = *Found;

		if (Record.State == ECommissionState::Accepted)
		{
			OfferedCommissions.Remove(Commission);
			AcceptedCommissions.Add(Commission);
			Commission->OnCompleted.AddUObject(this, &UMissionSubsystem::HandleCommissionCompleted);
			Commission->RestoreState(ECommissionState::Accepted, Player);
		}
		else if (Record.State == ECommissionState::Completed)
		{
			OfferedCommissions.Remove(Commission);
			CompletedCommissions.Add(Commission);
			Commission->RestoreState(ECommissionState::Completed, Player);
		}
	}
}

void UMissionSubsystem::ExpireAccepted()
{
	for (UCommission* Commission : AcceptedCommissions)
	{
		if (!Commission)
			continue;

		Commission->OnCompleted.RemoveAll(this);
		Commission->Expire();
		// TODO(§13.4): apply failurePenalty here once reputation / followers exist.
	}

	AcceptedCommissions.Reset();
	CompletedCommissions.Reset();
}

void UMissionSubsystem::HandleCommissionCompleted(UCommission* Commission)
{
	AcceptedCommissions.Remove(Commission);
	CompletedCommissions.AddUnique(Commission);

	ApplyReward(Commission->GetReward());

	PersistState();
	OnCommissionCompleted.Broadcast(Commission);
	OnBoardChanged.Broadcast();
}

void UMissionSubsystem::ApplyReward(const FCommissionReward& Reward)
{
	// Money wires instantly to the save (§23 #23).
	if (UGlobalSaveGameData* Save = GetSave())
		Save->Money += Reward.Money;

	// XP credits to the shared pool (currently a float on the character; §7.10 GAS migration later).
	if (ANakedDesireCharacter* Player = GetPlayer())
		Player->XP += Reward.XP;

	// TODO: followers — no follower / profile system yet (Phase 8); Reward.Followers is dropped for now.
}

void UMissionSubsystem::PersistState() const
{
	UGlobalSaveGameData* Save = GetSave();
	if (!Save)
		return;

	TArray<FCommissionSaveRecord> Records;
	for (const UCommission* Commission : AcceptedCommissions)
	{
		if (Commission)
			Records.Add({ Commission->CommissionId, ECommissionState::Accepted });
	}
	for (const UCommission* Commission : CompletedCommissions)
	{
		if (Commission)
			Records.Add({ Commission->CommissionId, ECommissionState::Completed });
	}

	Save->SetCommissionRecords(Records);
}

ANakedDesireCharacter* UMissionSubsystem::GetPlayer() const
{
	return Cast<ANakedDesireCharacter>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
}

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

UCommissionBoardConfig* UMissionSubsystem::GetBoardConfig() const
{
	const UNakedDesireGameInstance* GameInstance =
		Cast<UNakedDesireGameInstance>(GetWorld() ? GetWorld()->GetGameInstance() : nullptr);
	return GameInstance ? GameInstance->CommissionBoard : nullptr;
}