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

#include "NakedDesireCharacter.h"
#include "NakedDesire/Locations/LocationTrigger.h"
#include "NakedDesire/Clothing/ClothingManager.h"
#include "Components/CapsuleComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "NakedDesire/InteractionSystem/InteractionManager.h"
#include "NakedDesire/InteractionSystem/InteractionTarget.h"
#include "NakedDesire/MissionBuilder/MissionsManager.h"
#include "NakedDesire/Stats/StatsManager.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "Kismet/GameplayStatics.h"
#include "Internationalization/Text.h"
#include "NakedDesire/Clothing/ClothingItem.h"
#include "NakedDesire/Clothing/ClothingItemInstance.h"
#include "NakedDesire/Global/Constants.h"
#include "NakedDesire/Global/NakedDesireUserSettings.h"
#include "Perception/AIPerceptionStimuliSourceComponent.h"
#include "Perception/AISense_Sight.h"

ANakedDesireCharacter::ANakedDesireCharacter()
{
	GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
	
	GetCapsuleComponent()->OnComponentBeginOverlap.AddUniqueDynamic(this, &ANakedDesireCharacter::OnBeginOverlap);
	GetCapsuleComponent()->OnComponentEndOverlap.AddUniqueDynamic(this, &ANakedDesireCharacter::OnEndOverlap);

	bUseControllerRotationYaw = false;

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

	ClothingManager = CreateDefaultSubobject<UClothingManager>("Clothing Manager");
	StatsManager = CreateDefaultSubobject<UStatsManager>("Stats Manager");
	MissionsManager = CreateDefaultSubobject<UMissionsManager>("Missions Manager");
	InteractionManager = CreateDefaultSubobject<UInteractionManager>("Interaction Manager");

	NipplesMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Nipples");
	NipplesMeshComponent->SetupAttachment(GetMesh());
	AnalMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Anal");
	AnalMeshComponent->SetupAttachment(GetMesh());
	VaginaMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Vagina");
	VaginaMeshComponent->SetupAttachment(GetMesh());
	HeadMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Head");
	HeadMeshComponent->SetupAttachment(GetMesh());
	NeckMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Neck");
	NeckMeshComponent->SetupAttachment(GetMesh());
	FaceMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Face");
	FaceMeshComponent->SetupAttachment(GetMesh());
	EyesMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Eyes");
	EyesMeshComponent->SetupAttachment(GetMesh());
	BodyMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Body");
	BodyMeshComponent->SetupAttachment(GetMesh());
	TopMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Top");
	TopMeshComponent->SetupAttachment(GetMesh());
	BottomMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Bottom");
	BottomMeshComponent->SetupAttachment(GetMesh());
	BraMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Bra");
	BraMeshComponent->SetupAttachment(GetMesh());
	PantiesMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Panties");
	PantiesMeshComponent->SetupAttachment(GetMesh());
	SocksMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Socks");
	SocksMeshComponent->SetupAttachment(GetMesh());
	ShoesMeshComponent = CreateDefaultSubobject<USkeletalMeshComponent>("Shoes");
	ShoesMeshComponent->SetupAttachment(GetMesh());

	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")));
	FrontBottomCensorship = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Front Bottom Censorship"));
	FrontBottomCensorship->SetupAttachment(GetMesh(), FName(TEXT("pelvis")));
	BackBottomCensorship = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Back Bottom Censorship"));
	BackBottomCensorship->SetupAttachment(GetMesh(), FName(TEXT("pelvis")));

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

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

EStance ANakedDesireCharacter::GetStance() const
{
	return Stance;
}

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);
	}
}

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(TSubclassOf<UAISense_Sight>());
	StimuliSourceComponent->RegisterWithPerceptionSystem();

	SetupClothingSlots();

	ClothingManager->OnClothingEquip.AddUniqueDynamic(this, &ANakedDesireCharacter::OnClothingEquip);
	ClothingManager->OnClothingUnequip.AddUniqueDynamic(this, &ANakedDesireCharacter::OnClothingUnequip);

	UNakedDesireUserSettings::GetNakedDesireUserSettings()->OnSettingsChanged.AddUniqueDynamic(this, &ANakedDesireCharacter::OnSettingsChanged);
}

UAISense_Sight::EVisibilityResult ANakedDesireCharacter::CanBeSeenFrom(const FCanBeSeenFromContext& Context,
	FVector& OutSeenLocation, int32& OutNumberOfLoSChecksPerformed, int32& OutNumberOfAsyncLosCheckRequested,
	float& OutSightStrength, int32* UserData, const FOnPendingVisibilityQueryProcessedDelegate* Delegate)
{
	const FVector StartLocation = Context.ObserverLocation;
	const FVector BoobsBoneLocation = GetMesh()->GetBoneLocation(FName(TEXT("boobs_root")));
	const FVector PelvisBoneLocation = GetMesh()->GetBoneLocation(FName(TEXT("pelvis")));
	
	OutNumberOfLoSChecksPerformed++;

	FHitResult BoobsHitResult;
	const bool BoobsHit = CheckSight(StartLocation, BoobsBoneLocation, BoobsHitResult, Context.IgnoreActor);
	
	FHitResult PelvisHitResult;
	const bool PelvisHit = CheckSight(StartLocation, PelvisBoneLocation, PelvisHitResult, Context.IgnoreActor);

	if ((!BoobsHit || BoobsHitResult.GetActor() == this) && ClothingManager->IsBodyTypeExposed(EPrivateBodyPartType::FrontTop))
	{
		UE_LOG(LogTemp, Warning, TEXT("Boobs hit"));
		OutSeenLocation = BoobsBoneLocation;
		OutSightStrength = 1.0f;
		return UAISense_Sight::EVisibilityResult::Visible;
	}

	if ((!PelvisHit || PelvisHitResult.GetActor() == this) &&
			(ClothingManager->IsBodyTypeExposed(EPrivateBodyPartType::BackBottom) || ClothingManager->IsBodyTypeExposed(EPrivateBodyPartType::FrontBottom)))
	{
		UE_LOG(LogTemp, Warning, TEXT("Pelvis hit"));
		OutSeenLocation = PelvisBoneLocation;
		OutSightStrength = 1.0f;
		return UAISense_Sight::EVisibilityResult::Visible;
	}

	UE_LOG(LogTemp, Warning, TEXT("Not visoble"));

	return UAISense_Sight::EVisibilityResult::NotVisible;
}

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::OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
                                           UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	if (OtherActor->Implements<UInteractionTarget>())
	{
		InteractionManager->OnTargetEnter(OtherActor);
	}

	if (const ALocationTrigger* AreaTrigger = Cast<ALocationTrigger>(OtherActor))
	{
		CurrentArea = AreaTrigger->GetLocationData();
		OnAreaEnter.Broadcast(CurrentArea);
	}
}

void ANakedDesireCharacter::OnEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
	UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	if (OtherActor->Implements<UInteractionTarget>())
	{
		InteractionManager->OnTargetExit(OtherActor);
	}

	if (const ALocationTrigger* AreaTrigger = Cast<ALocationTrigger>(OtherActor))
	{
		CurrentArea = nullptr;
		OnAreaExit.Broadcast(AreaTrigger->GetLocationData());
	}
}

void ANakedDesireCharacter::OnClothingEquip(const UClothingItemInstance* ClothingItemInstance)
{
	if (ClothingItemInstance->GetClothingItem()->CoveredBodyParts.Contains(EPrivateBodyPartType::BackBottom))
	{
		BackBottomCensorship->SetVisibility(false);
	}
	if (ClothingItemInstance->GetClothingItem()->CoveredBodyParts.Contains(EPrivateBodyPartType::FrontBottom))
	{
		FrontBottomCensorship->SetVisibility(false);
	}
	if (ClothingItemInstance->GetClothingItem()->CoveredBodyParts.Contains(EPrivateBodyPartType::FrontTop))
	{
		BoobLCensorship->SetVisibility(false);
		BoobRCensorship->SetVisibility(false);
	}
}

void ANakedDesireCharacter::OnClothingUnequip(const UClothingItemInstance* ClothingItemInstance)
{
	if (!UNakedDesireUserSettings::GetNakedDesireUserSettings()->GetIsCensorshipEnabled() && !IS_DEMO)
		return;
	
	if (ClothingManager->IsBodyTypeExposed(EPrivateBodyPartType::BackBottom))
	{
		BackBottomCensorship->SetVisibility(true);
	}
	if (ClothingManager->IsBodyTypeExposed(EPrivateBodyPartType::FrontBottom))
	{
		FrontBottomCensorship->SetVisibility(true);
	}
	if (ClothingManager->IsBodyTypeExposed(EPrivateBodyPartType::FrontTop))
	{
		BoobLCensorship->SetVisibility(true);
		BoobRCensorship->SetVisibility(true);
	}
}

void ANakedDesireCharacter::OnSettingsChanged(UNakedDesireUserSettings* Settings)
{
	if (Settings->GetIsCensorshipEnabled())
	{
		if (ClothingManager->IsBodyTypeExposed(EPrivateBodyPartType::BackBottom))
		{
			BackBottomCensorship->SetVisibility(true);
		}
		if (ClothingManager->IsBodyTypeExposed(EPrivateBodyPartType::FrontBottom))
		{
			FrontBottomCensorship->SetVisibility(true);
		}
		if (ClothingManager->IsBodyTypeExposed(EPrivateBodyPartType::FrontTop))
		{
			BoobLCensorship->SetVisibility(true);
			BoobRCensorship->SetVisibility(true);
		}
	}
	else if (!IS_DEMO)
	{
		BackBottomCensorship->SetVisibility(false);
		FrontBottomCensorship->SetVisibility(false);
		BoobLCensorship->SetVisibility(false);
		BoobRCensorship->SetVisibility(false);
	}
}

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::SetupClothingSlots()
{
#define LOCTEXT_NAMESPACE "ClothingSlots"
	
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			NipplesMeshComponent, EClothingSlotType::Nipples,
			nullptr,
			LOCTEXT("Nipples", "Nipples")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			AnalMeshComponent, EClothingSlotType::Anal,
			nullptr,
			LOCTEXT("Anal", "Anal")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			VaginaMeshComponent, EClothingSlotType::Vagina,
			nullptr,
			LOCTEXT("Vagina", "Vagina")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			HeadMeshComponent, EClothingSlotType::Head,
			nullptr,
			LOCTEXT("Head", "Head")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			NeckMeshComponent, EClothingSlotType::Neck,
			nullptr,
			LOCTEXT("Neck", "Neck")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			FaceMeshComponent, EClothingSlotType::Face,
			nullptr,
			LOCTEXT("Face", "Face")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			EyesMeshComponent, EClothingSlotType::Eyes,
			nullptr,
			LOCTEXT("Eyes", "Eyes")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			BodyMeshComponent, EClothingSlotType::Body,
			nullptr,
			LOCTEXT("Body", "Body")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			TopMeshComponent, EClothingSlotType::Top,
			nullptr,
			LOCTEXT("Top", "Top")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			BottomMeshComponent, EClothingSlotType::Bottom,
			nullptr,
			LOCTEXT("Bottom", "Bottom")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			BraMeshComponent, EClothingSlotType::Bra,
			nullptr,
			LOCTEXT("Bra", "Bra")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			PantiesMeshComponent, EClothingSlotType::Panties,
			nullptr,
			LOCTEXT("Panties", "Panties")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			SocksMeshComponent, EClothingSlotType::Socks,
			nullptr,
			LOCTEXT("Socks", "Socks")));
	ClothingManager->ClothingSlots.Add(
		FClothingSlotData(
			ShoesMeshComponent, EClothingSlotType::Shoes,
			nullptr,
			LOCTEXT("Shoes", "Shoes")));

#undef LOCTEXT_NAMESPACE
}

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;
}
