// Copyright ExByte Studios. Item Interaction Ecosystem.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "GameplayTagContainer.h"
#include "ItemEnums.h"
#include "ItemInstanceData.h"
#include "ItemInteractionInterfaces.h"
#include "HeldItemComponent.generated.h"

class APickupItemActor;

/** Replicated snapshot of what the character is currently holding (section 11.1). */
USTRUCT(BlueprintType)
struct ITEMWORLD_API FHeldItemState
{
	GENERATED_BODY()

	UPROPERTY(BlueprintReadOnly, Category = "Held")
	bool bHasHeldItem = false;

	UPROPERTY(BlueprintReadOnly, Category = "Held")
	FGuid InstanceId;

	UPROPERTY(BlueprintReadOnly, Category = "Held")
	FName ItemId;

	UPROPERTY(BlueprintReadOnly, Category = "Held")
	EHeldItemMode Mode = EHeldItemMode::None;

	UPROPERTY(BlueprintReadOnly, Category = "Held")
	TObjectPtr<AActor> HeldWorldActor = nullptr;

	UPROPERTY(BlueprintReadOnly, Category = "Held")
	FName HoldSocket = NAME_None;

	UPROPERTY(BlueprintReadOnly, Category = "Held")
	float MoveSpeedMultiplier = 1.f;

	UPROPERTY(BlueprintReadOnly, Category = "Held")
	bool bBlocksSprint = false;

	UPROPERTY(BlueprintReadOnly, Category = "Held")
	bool bBlocksClimb = false;

	/** Snapshot of the carried item (so we can drop/install it without the actor). */
	UPROPERTY(BlueprintReadOnly, Category = "Held")
	FItemInstanceData Item;
};

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHeldItemChanged, const FHeldItemState&, NewState);

/**
 * Physically carries one bulky item attached to the character (section 11.2-11.3).
 *
 * This is the "two-handed carry" path for batteries, canisters, wheels, crates,
 * corpses, etc. - distinct from equipment (which keeps the item in the inventory
 * and only spawns a visual). Server-authoritative; visuals/penalties apply on all
 * clients via OnRep. Implements IHeldItemInterface so the resolver can build
 * held+target interactions.
 */
UCLASS(ClassGroup = (Inventory), meta = (BlueprintSpawnableComponent))
class ITEMWORLD_API UHeldItemComponent : public UActorComponent, public IHeldItemInterface
{
	GENERATED_BODY()

public:
	UHeldItemComponent();

	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Held")
	FName CarrySocket = FName("CarrySocket");

	/** Hold distance in front of the view for physics-handle carry (section 11.4). */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Held")
	float PhysicsHoldDistance = 200.f;

	UPROPERTY(BlueprintAssignable, Category = "Held")
	FOnHeldItemChanged OnHeldItemChanged;

	UFUNCTION(BlueprintPure, Category = "Held")
	bool HasHeldItemBP() const { return HeldItem.bHasHeldItem; }

	UFUNCTION(BlueprintPure, Category = "Held")
	const FHeldItemState& GetHeldItemState() const { return HeldItem; }

	UFUNCTION(BlueprintPure, Category = "Held")
	bool CanHoldItem(const FItemInstanceData& Item) const;

	/** Client entry: ask the server to drop the carried item (bind to the Drop input). */
	UFUNCTION(BlueprintCallable, Category = "Held")
	void RequestDrop();

	// --- Server entry points (section 11.1) ---

	/** Server: pick up and carry a world item actor attached to a socket (World -> Held). */
	bool ServerHoldWorldItem(AActor* WorldItem);

	/** Server: carry a world item via a physics handle instead of a fixed socket (section 11.4). */
	bool ServerHoldWorldItemPhysics(AActor* WorldItem);

	/** Server: drop the carried item back into the world (Held -> World). */
	bool ServerDropHeldItem();

	/** Server: relinquish the carried item without spawning a drop (e.g. on install). */
	bool ServerConsumeHeldItem(FItemInstanceData& OutItem);

	/**
	 * Server: hand off the carried item AND its actor to a new owner (e.g. a world
	 * slot) without destroying the actor. Clears the held state. Returns false if
	 * nothing was held.
	 */
	bool ServerHandOffHeldItem(FItemInstanceData& OutItem, AActor*& OutActor);

	/** Server: update the carried item's data in place (e.g. after pouring out fuel). */
	bool ServerUpdateHeldItem(const FItemInstanceData& NewItem);

	//~ IHeldItemInterface
	virtual bool GetHeldItem(FItemInstanceData& OutItem) const override;
	virtual bool HasHeldItem() const override { return HeldItem.bHasHeldItem; }

protected:
	UPROPERTY(ReplicatedUsing = OnRep_HeldItem)
	FHeldItemState HeldItem;

	UFUNCTION(Server, Reliable)
	void Server_Drop();

	UFUNCTION()
	void OnRep_HeldItem(FHeldItemState OldState);

	/** Applies/removes the movement-speed penalty on the owning character. */
	void ApplyMovementState();

	bool HasAuthority() const;

private:
	/** Owning character's unmodified MaxWalkSpeed, captured the first time we modify it. */
	UPROPERTY(Transient)
	float CachedBaseWalkSpeed = 0.f;

	/** Created on demand for physics-handle carry. */
	UPROPERTY(Transient)
	TObjectPtr<class UPhysicsHandleComponent> PhysicsHandle = nullptr;

	void UpdatePhysicsCarryTarget();
};
