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

#include "NakedDesireCharacter.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "NakedDesire/Stats/StatsManager.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "Kismet/GameplayStatics.h"
#include "NakedDesire/Censorship/CensorshipComponent.h"
#include "NakedDesire/Clothing/ClothingItemDefinition.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Clothing/ClothingVisualsComponent.h"
#include "NakedDesire/Global/NakedDesireHUD.h"
#include "NakedDesire/Interaction/InteractionComponent.h"
#include "NakedDesire/UI/GameLayoutWidget.h"
#include "Perception/AIPerceptionStimuliSourceComponent.h"
#include "Perception/AISense_Sight.h"

ANakedDesireCharacter::ANakedDesireCharacter()
{
	GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;

	bUseControllerRotationYaw = false;

	GetCharacterMovement()->bOrientRotationToMovement = true;
	GetCharacterMovement()->GetNavAgentPropertiesRef().bCanCrouch = true;

	ClothingManager = CreateDefaultSubobject<UClothingManager>("Clothing Manager");
	StatsManager = CreateDefaultSubobject<UStatsManager>("Stats Manager");

	ClothingVisualsComponent = CreateDefaultSubobject<UClothingVisualsComponent>(TEXT("Clothing Visuals Component"));

	BoobLCensorship = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Boob L Censorship"));
	BoobLCensorship->SetupAttachment(GetMesh(), FName(TEXT("boob_l")));
	BoobRCensorship = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Boob R Censorship"));
	BoobRCensorship->SetupAttachment(GetMesh(), FName(TEXT("boob_r")));
	VaginaCensorship = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Vagina Censorship"));
	VaginaCensorship->SetupAttachment(GetMesh(), FName(TEXT("pelvis")));
	AnalCensorship = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Anal Censorship"));
	AnalCensorship->SetupAttachment(GetMesh(), FName(TEXT("pelvis")));

	CensorshipComponent = CreateDefaultSubobject<UCensorshipComponent>(TEXT("Censorship Component"));

	StimuliSourceComponent = CreateDefaultSubobject<UAIPerceptionStimuliSourceComponent>(TEXT("Stimuli Source Component"));
	InteractionComponent = CreateDefaultSubobject<UInteractionComponent>(TEXT("Interaction Component"));
}

EGait ANakedDesireCharacter::GetGait() const
{
	return Gait;
}

void ANakedDesireCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	
	if (StatsManager->Stamina == 0 && Gait == EGait::Run)
	{
		SetIsRunning(false);
	}

	if (Gait == EGait::Run && GetCharacterMovement()->Velocity.SizeSquared2D() > 100 && StatsManager->Stamina > 0)
	{
		GetCharacterMovement()->MaxWalkSpeed = RunSpeed;
		StatsManager->DecreaseStamina(7 * DeltaTime);
	}
	else
	{
		GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
		if (StatsManager->Stamina < StatsManager->MaxStamina)
		{
			StatsManager->IncreaseStamina(15 * DeltaTime);
		}
	}
}

void ANakedDesireCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
	{
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &ANakedDesireCharacter::OnLook);
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ANakedDesireCharacter::OnMove);
		EnhancedInputComponent->BindAction(RunAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnRunPress);
		EnhancedInputComponent->BindAction(RunAction, ETriggerEvent::Completed, this, &ANakedDesireCharacter::OnRunRelease);
		EnhancedInputComponent->BindAction(CrouchAction, ETriggerEvent::Completed, this, &ANakedDesireCharacter::OnCrouchToggle);
		EnhancedInputComponent->BindAction(EquipmentAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnEquipmentPress);
		EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnInteractPress);
		EnhancedInputComponent->BindAction(PhoneAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnPhonePress);
		EnhancedInputComponent->BindAction(PauseAction, ETriggerEvent::Started, this, &ANakedDesireCharacter::OnPausePress);
	}
}

void ANakedDesireCharacter::NotifyControllerChanged()
{
	Super::NotifyControllerChanged();

	if (const APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0))
	{
		if (const ULocalPlayer* LocalPlayer = PC->GetLocalPlayer())
		{
			if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
			{
				if (MappingContext)
				{
					InputSystem->AddMappingContext(MappingContext, 0);
				}
			}
		}
	}
}

void ANakedDesireCharacter::BeginPlay()
{
	Super::BeginPlay();

	StimuliSourceComponent->RegisterForSense(UAISense_Sight::StaticClass());
	StimuliSourceComponent->RegisterWithPerceptionSystem();

	// Initialize after Super::BeginPlay so clothing hydration has populated the
	// equipped set; both components sync from that final state.
	ClothingVisualsComponent->Initialize(GetMesh(), ClothingManager);
	CensorshipComponent->Initialize(ClothingManager, BoobLCensorship, BoobRCensorship, VaginaCensorship, AnalCensorship);

	HUD = Cast<ANakedDesireHUD>(UGameplayStatics::GetPlayerController(GetWorld(), 0)->GetHUD());
}

UAISense_Sight::EVisibilityResult ANakedDesireCharacter::CanBeSeenFrom(const FCanBeSeenFromContext& Context,
	FVector& OutSeenLocation, int32& OutNumberOfLoSChecksPerformed, int32& OutNumberOfAsyncLosCheckRequested,
	float& OutSightStrength, int32* UserData, const FOnPendingVisibilityQueryProcessedDelegate* Delegate)
{
	OutNumberOfLoSChecksPerformed++;

	bool bTopVisible = false;
	bool bBottomVisible = false;
	const float Exposure = ComputeObservedExposure(Context.ObserverLocation, Context.IgnoreActor, bTopVisible, bBottomVisible);

	// An observer perceives the player only when a revealing body part is within their line of sight.
	if (Exposure > 0.0f)
	{
		const bool bSeenTop = bTopVisible && RevealAmount(EBodyPart::Boobs) > 0.0f;
		OutSeenLocation = GetMesh()->GetBoneLocation(FName(bSeenTop ? TEXT("boobs_root") : TEXT("pelvis")));
		OutSightStrength = 1.0f;
		return UAISense_Sight::EVisibilityResult::Visible;
	}

	return UAISense_Sight::EVisibilityResult::NotVisible;
}

float ANakedDesireCharacter::GetObservedExposureFrom(const FVector& ObserverLocation, const AActor* IgnoreActor)
{
	bool bTopVisible = false;
	bool bBottomVisible = false;
	return ComputeObservedExposure(ObserverLocation, IgnoreActor, bTopVisible, bBottomVisible);
}

float ANakedDesireCharacter::ComputeObservedExposure(const FVector& ObserverLocation, const AActor* IgnoreActor,
	bool& bOutTopVisible, bool& bOutBottomVisible)
{
	const FVector BoobsBoneLocation = GetMesh()->GetBoneLocation(FName(TEXT("boobs_root")));
	const FVector PelvisBoneLocation = GetMesh()->GetBoneLocation(FName(TEXT("pelvis")));

	float Exposure = 0.0f;

	// Top region -> Boobs. A region is "visible" when its LOS trace is unobstructed (or only hits self).
	FHitResult TopHit;
	bOutTopVisible = !CheckSight(ObserverLocation, BoobsBoneLocation, TopHit, IgnoreActor) || TopHit.GetActor() == this;
	if (bOutTopVisible)
		Exposure += RevealAmount(EBodyPart::Boobs);

	// Bottom region (pelvis) -> Ass + Genitals; one trace gates both lower parts.
	FHitResult BottomHit;
	bOutBottomVisible = !CheckSight(ObserverLocation, PelvisBoneLocation, BottomHit, IgnoreActor) || BottomHit.GetActor() == this;
	if (bOutBottomVisible)
	{
		Exposure += RevealAmount(EBodyPart::Ass);
		Exposure += RevealAmount(EBodyPart::Genitals);
	}

	return Exposure;
}

float ANakedDesireCharacter::RevealAmount(const EBodyPart BodyPart)
{
	const float Coverage = ClothingManager->GetEffectiveCoverage(BodyPart);
	return Coverage < ObservationRevealThreshold ? (1.0f - Coverage) : 0.0f;
}

bool ANakedDesireCharacter::CheckSight(const FVector& StartLocation, const FVector& EndLocation, FHitResult& HitResult,
	const AActor* IgnoreActor)
{
	FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(AILineOfSight), true);
	QueryParams.AddIgnoredActor(IgnoreActor);
	QueryParams.AddIgnoredActor(this); // ignore self

	const bool bHit = GetWorld()->LineTraceSingleByChannel(
		HitResult,
		StartLocation,
		EndLocation,
		ECC_Visibility,
		QueryParams
	);

	// DrawDebugLine(GetWorld(), StartLocation, EndLocation, bHit ? FColor::Red : FColor::Green, false, 1.0f);

	return bHit;
}

void ANakedDesireCharacter::LogTest()
{
	UClothingItemInstance* ClothingItemInstance = ClothingManager->GetSlotClothing(EClothingSlotType::UnderwearTop);
	UE_LOG(LogTemp, Warning, TEXT("ANakedDesireCharacter::LogTest %s"), *ClothingItemInstance->GetInstanceId().ToString());
}

void ANakedDesireCharacter::OnLook(const FInputActionValue& Value)
{
	const FVector2D MouseValue = Value.Get<FVector2D>();

	AddControllerYawInput(MouseValue.X);
	AddControllerPitchInput(MouseValue.Y);
}

void ANakedDesireCharacter::OnMove(const FInputActionValue& Value)
{
	const FVector2D MoveInput = Value.Get<FVector2D>();
	const FRotator Rotator = Controller->GetControlRotation();
	const FRotationMatrix Direction = FRotationMatrix(FRotator(0, Rotator.Yaw, 0));
	const FVector ForwardDirection = Direction.GetUnitAxis(EAxis::X);
	const FVector RightDirection = Direction.GetUnitAxis(EAxis::Y);

	AddMovementInput(ForwardDirection, MoveInput.Y);
	AddMovementInput(RightDirection, MoveInput.X);
}

void ANakedDesireCharacter::OnRunPress(const FInputActionValue& Value)
{
	SetIsRunning(true);
}

void ANakedDesireCharacter::OnRunRelease(const FInputActionValue& Value)
{
	SetIsRunning(false);
}

void ANakedDesireCharacter::OnCrouchToggle(const FInputActionValue& Value)
{
	if (GetCharacterMovement()->IsCrouching())
	{
		UnCrouch();
	}
	else
	{
		Crouch();
	}
}

void ANakedDesireCharacter::OnEquipmentPress(const FInputActionValue& Value)
{
	HUD->GetGameLayoutWidget()->OpenInventory();
}

void ANakedDesireCharacter::OnInteractPress(const FInputActionValue& Value)
{
	InteractionComponent->Interact();
}

void ANakedDesireCharacter::OnPhonePress(const FInputActionValue& Value)
{
	HUD->GetGameLayoutWidget()->OpenPhone();
}

void ANakedDesireCharacter::OnPausePress(const FInputActionValue& Value)
{
	HUD->GetGameLayoutWidget()->OpenPauseMenu();
}

void ANakedDesireCharacter::NotifyNoticed(ANPCAIController* NPCController)
{
	OnNoticed.Broadcast(NPCController);
}

void ANakedDesireCharacter::SetIsRunning(const bool Value)
{
	Gait = Value ? EGait::Run : EGait::Walk;
	GetCharacterMovement()->MaxWalkSpeed = Gait == EGait::Run ? RunSpeed : WalkSpeed;
}
