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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputAction.h"
#include "InputMappingContext.h"
#include "NakedDesire/Clothing/BodyPart.h"
#include "NakedDesire/Global/Gait.h"
#include "NakedDesire/Global/Stance.h"
#include "Perception/AISightTargetInterface.h"
#include "NakedDesireCharacter.generated.h"

class UInteractionComponent;
class ANakedDesireHUD;
class UClothingItemDefinition;
class UClothingItemInstance;
class UClothingSlotsData;
class UAIPerceptionStimuliSourceComponent;
class UClothingManager;
class UClothingVisualsComponent;
class UCensorshipComponent;
class UStatsManager;
class ANPCAIController;
class ULocationData;

DECLARE_MULTICAST_DELEGATE_OneParam(FOnNoticedSignature, ANPCAIController*);

UCLASS(config=Game)
class ANakedDesireCharacter : public ACharacter, public IAISightTargetInterface
{
	GENERATED_BODY()

public:
	ANakedDesireCharacter();

	// Input
	UPROPERTY(EditDefaultsOnly, Category = "Input")
	UInputMappingContext* MappingContext;
	
	UPROPERTY(EditDefaultsOnly, Category = "Input")
	UInputAction* LookAction;
	
	UPROPERTY(EditDefaultsOnly, Category = "Input")
	UInputAction* MoveAction;
	
	UPROPERTY(EditDefaultsOnly, Category = "Input")
	UInputAction* RunAction;
	
	UPROPERTY(EditDefaultsOnly, Category = "Input")
	UInputAction* CrouchAction;
	
	UPROPERTY(EditDefaultsOnly, Category = "Input")
	UInputAction* EquipmentAction;
	
	UPROPERTY(EditDefaultsOnly, Category = "Input")
	UInputAction* InteractAction;

	// Temporary dev entry to open the phone; replace with phone-slot interaction in Phase 8 (GDD §9 / §6.5).
	UPROPERTY(EditDefaultsOnly, Category = "Input")
	UInputAction* PhoneAction;

	UPROPERTY(EditDefaultsOnly, Category = "Input")
	UInputAction* PauseAction;

	// Clothing slot meshes are spawned per equipped slot at runtime by ClothingVisualsComponent.
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Clothing")
	UClothingVisualsComponent* ClothingVisualsComponent;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Censorship")
	UStaticMeshComponent* BoobLCensorship;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Censorship")
	UStaticMeshComponent* BoobRCensorship;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Censorship")
	UStaticMeshComponent* VaginaCensorship;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Censorship")
	UStaticMeshComponent* AnalCensorship;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Censorship")
	UCensorshipComponent* CensorshipComponent;

	UPROPERTY(EditDefaultsOnly, Category = "Clothing")
	TObjectPtr<UClothingSlotsData> SlotsData;

	// Components
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Clothing")
	UClothingManager* ClothingManager;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	UStatsManager* StatsManager;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	UAIPerceptionStimuliSourceComponent* StimuliSourceComponent;
	
	UPROPERTY(EditDefaultsOnly, Category = "Interaction")
	TObjectPtr<UInteractionComponent> InteractionComponent;
	
	// Variables
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	float WalkSpeed = 250.0f;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	float RunSpeed = 500.0f;
	
	float XP = 0.0f;

	UPROPERTY()
	ULocationData* CurrentArea = nullptr;

	FOnNoticedSignature OnNoticed;

	void NotifyNoticed(ANPCAIController* NPCController);
	void SetIsRunning(bool Value);

	UFUNCTION(BlueprintPure)
	EGait GetGait() const;

	virtual void Tick(float DeltaTime) override;
	virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
	virtual void NotifyControllerChanged() override;
	virtual void BeginPlay() override;
	virtual UAISense_Sight::EVisibilityResult CanBeSeenFrom(const FCanBeSeenFromContext& Context, FVector& OutSeenLocation, int32& OutNumberOfLoSChecksPerformed, int32& OutNumberOfAsyncLosCheckRequested, float& OutSightStrength, int32* UserData = nullptr, const FOnPendingVisibilityQueryProcessedDelegate* Delegate = nullptr) override;

	// Player exposure as read by an observer at ObserverLocation, in [0, 3]: the sum over the
	// LOS-visible body regions (top -> Boobs, bottom -> Ass + Genitals) of (1 - effective coverage)
	// for parts below ObservationRevealThreshold. 0 means nothing revealing is visible to them.
	// Queried per observing NPC each tick by UStatsManager to drive embarrassment (GDD §7.1).
	float GetObservedExposureFrom(const FVector& ObserverLocation, const AActor* IgnoreActor);

	// Below this effective coverage [0,1] a body part reads as "revealing": observers notice it and
	// it contributes to embarrassment. At or above it the part is treated as decently covered.
	UPROPERTY(EditDefaultsOnly, Category = "Observation")
	float ObservationRevealThreshold = 0.9f;

private:
	EGait Gait = EGait::Walk;
	EStance Stance = EStance::Stand;

	void OnLook(const FInputActionValue& Value);
	void OnMove(const FInputActionValue& Value);
	void OnRunPress(const FInputActionValue& Value);
	void OnRunRelease(const FInputActionValue& Value);
	void OnCrouchToggle(const FInputActionValue& Value);
	void OnEquipmentPress(const FInputActionValue& Value);
	void OnInteractPress(const FInputActionValue& Value);
	void OnPhonePress(const FInputActionValue& Value);
	void OnPausePress(const FInputActionValue& Value);

	bool CheckSight(const FVector& StartLocation, const FVector& EndLocation, FHitResult& HitResult, const AActor* IgnoreActor);

	// Traces observer->boobs_root and observer->pelvis for line of sight (top / bottom regions),
	// then sums (1 - coverage) over the revealing parts in each LOS-visible region. The out-bools
	// report raw line of sight per region (before coverage), used to pick a seen location.
	float ComputeObservedExposure(const FVector& ObserverLocation, const AActor* IgnoreActor, bool& bOutTopVisible, bool& bOutBottomVisible);

	// (1 - effective coverage) when BodyPart is below ObservationRevealThreshold, else 0.
	float RevealAmount(EBodyPart BodyPart);
	
	UFUNCTION(Exec)
	void LogTest();
	
	UPROPERTY()
	TObjectPtr<ANakedDesireHUD> HUD;
};
