// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "BoostComponent.h"
#include "ESonicCharacterState.h"
#include "HomingAttackComponent.h"
#include "LightSpeedDashComponent.h"
#include "WallJumpComponent.h"
#include "PickUpComponent.h"
#include "SonicCameraComponent.h"
#include "SonicMovementComponent.h"
#include "SpeedActorInteractionsComponent.h"
#include "SonicCharacterComponent.generated.h"

class UCameraComponent;

UCLASS()
class SONICUNLEASHEDREMAKE_API ASonicCharacterComponent : public ACharacter
{
	GENERATED_BODY()

public:
	// Sets default values for this character's properties
	ASonicCharacterComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Sonic")
		EFEeSonicCharacterState CurrentState;

	

	//components
	UPROPERTY(VisibleAnywhere,BlueprintReadOnly, Category = "Boost")
		UBoostComponent* BoostComponent;
	UPROPERTY(VisibleAnywhere,BlueprintReadOnly, Category = "HomingAttack")
		UHomingAttackComponent* HomingAttackComponent;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "SpeedItemInteractions")
		USpeedActorInteractions* SpeedActorInteractionsComponent;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "PickUpComponent")
		UPickUpComponent* PickUpComponent;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "LightSpeedDashComponent")
		ULightSpeedDashComponent* LightSpeedDashComponent;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "WallJumpComponent")
		UWallJumpComponent* WallJumpComponent;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Sonic")
		USonicMovementComponent* SonicMovement;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CameraComponent")
		USonicCameraComponent* SonicCameraComponent;
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Sonic Movement")
	UArrowComponent* FancyArrow;



protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Sonic")
	USpringArmComponent* SpringArmComponent;

	
	void StatusHandler();
	bool StateInAir();

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	void MoveForward(float Value);
	void MoveRight(float Value);
	// Called to bind functionality to input
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

	virtual __override void Jump() override;
	virtual __override void StopJumping() override;
	virtual __override void Landed(const FHitResult& Hit) override;
	
	//functions to be called in the blueprint
	
	UFUNCTION(BlueprintCallable, Category = "Sonic") // launch sonic can be found in the movement component
		void LaunchSonic(float Speed, FVector Direction, float OutOfControlTime, float KeepVelocityTime,
			bool bAlignToMovementDirection, float HomingCooldown, bool bCanHomingAttackAfterLaunch = true, bool bCanAirDashAfterLaunch = true, bool bMaintainMomentum = false);

	
	UFUNCTION(BlueprintCallable, Category = "Sonic")
		void SetLocationAboveActor(AActor* ActorToStandAbove, float HightOffset);
	

	UPROPERTY(Blueprintable, BlueprintReadWrite, Category = "Sonic")
		bool bCanMove;
	


	





};


// Fill out your copyright notice in the Description page of Project Settings.


#include "Sonic/SonicCharacterComponent.h"

#include "Camera/CameraComponent.h"
#include "Sonic/SonicMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Kismet/GameplayStatics.h"

// Sets default values
ASonicCharacterComponent::ASonicCharacterComponent(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer.SetDefaultSubobjectClass<USonicMovementComponent>(ACharacter::CharacterMovementComponentName))
{

	//setup components
	BoostComponent = CreateDefaultSubobject<UBoostComponent>(TEXT("BoostComponent"));
	HomingAttackComponent = CreateDefaultSubobject<UHomingAttackComponent>(TEXT("HomingAttackComponent"));
	SpeedActorInteractionsComponent = CreateDefaultSubobject<USpeedActorInteractions>(TEXT("SpeedActorInteractions"));
	PickUpComponent = CreateDefaultSubobject<UPickUpComponent>(TEXT("PickUpComponent"));
	LightSpeedDashComponent = CreateDefaultSubobject<ULightSpeedDashComponent>(TEXT("LightSpeedDashComponent"));
	WallJumpComponent = CreateDefaultSubobject<UWallJumpComponent>(TEXT("WallJumpComponent"));
	SonicMovement = Cast<USonicMovementComponent>(GetCharacterMovement());
	SonicCameraComponent = CreateDefaultSubobject<USonicCameraComponent>(TEXT("SonicCameraComponent"));
	FancyArrow = CreateDefaultSubobject<UArrowComponent>(TEXT("DebugArrow"));
	PrimaryActorTick.bCanEverTick = true;
	CurrentState = EFEeSonicCharacterState::Idle; //set the current state to idle
	bCanMove = true;

	SpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
	SpringArmComponent->SetupAttachment(RootComponent);
	SpringArmComponent->TargetArmLength = 500.0f;
	SpringArmComponent->bUsePawnControlRotation = true;
}



// Called when the game starts or when spawned
void ASonicCharacterComponent::BeginPlay()
{
	Super::BeginPlay();
	if (SonicCameraComponent)
	{
		if (!SonicCameraComponent->Camera)
		{
			SonicCameraComponent->Camera = Cast<AUnleashedCamera>(UGameplayStatics::GetActorOfClass(GetWorld(), AUnleashedCamera::StaticClass()));
			if (!SonicCameraComponent->Camera)
			{
				SonicCameraComponent->Camera = GetWorld()->SpawnActor<AUnleashedCamera>(AUnleashedCamera::StaticClass());
			}
		}
		if (SpringArmComponent && SonicCameraComponent->Camera && SonicCameraComponent->Camera->GetCameraComponent())
		{
			SonicCameraComponent->Camera->GetCameraComponent()->SetupAttachment(SpringArmComponent);
			SonicCameraComponent->Camera->GetCameraComponent()->bUsePawnControlRotation = false;
			SonicCameraComponent->SetupCamera(this, SpringArmComponent, SonicCameraComponent->Camera);
			SonicCameraComponent->SwitchCameraSmooth(SonicCameraComponent->Camera, 0.0f, EViewTargetBlendFunction::VTBlend_Linear, 0.0f, false);
		}
		else
		{
			UE_LOG(LogTemp, Error, TEXT("ASonicCharacterComponent::BeginPlay - Missing required components"));
		}
	}
}

void ASonicCharacterComponent::StatusHandler()
{
	if (!SonicMovement) return;
	
	float CurrentSpeed = SonicMovement->GetForwardSpeed();
	bool bIsFalling = SonicMovement->IsFalling();

	constexpr float MovementThreshold = 1.0f;

	//print current state, and current speed
	GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Red, FString::Printf(TEXT("Current State: %s"), *UEnum::GetValueAsString(CurrentState)));
	GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Red, FString::Printf(TEXT("Current Speed: %f"), CurrentSpeed));

	if (CurrentSpeed > MovementThreshold && CurrentState == EFEeSonicCharacterState::Idle)//if idle then set to moving
	{
		if (CurrentState != EFEeSonicCharacterState::MovingOnGround)
		{
			CurrentState = EFEeSonicCharacterState::MovingOnGround;
		}
	}
	//if we are in ground movement state, but we stand still, set to idle
	else if (CurrentSpeed <= MovementThreshold && CurrentState == EFEeSonicCharacterState::MovingOnGround)
	{
		CurrentState = EFEeSonicCharacterState::Idle;
	}

	//if we are in ground movement, but we are in the air, set to falling
	if (CurrentState == EFEeSonicCharacterState::MovingOnGround && GetCharacterMovement()->IsFalling())
	{
		CurrentState = EFEeSonicCharacterState::Falling;
	}

	//if the state we are currently in is listed as an in air state, but we are no longer in the air, reset to idle
	if (StateInAir() && !GetCharacterMovement()->IsFalling())
	{
		CurrentState = EFEeSonicCharacterState::Idle;
		
	}
}

bool ASonicCharacterComponent::StateInAir()
{
	//if the state is either jumping or falling, return true
	if (CurrentState == EFEeSonicCharacterState::Jumping ||
		CurrentState == EFEeSonicCharacterState::Falling ||
		CurrentState == EFEeSonicCharacterState::HomingAttacking||
		CurrentState == EFEeSonicCharacterState::AirBoosting||
		CurrentState == EFEeSonicCharacterState::AirDashing)
		//don't include light speed dashing because we want to be able to dash from the ground
	{
		return true;
	}
	return false;
	
}

// Called every frame
void ASonicCharacterComponent::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	StatusHandler();
	FancyArrow->SetWorldLocation(GetActorLocation());
}


void ASonicCharacterComponent::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	PlayerInputComponent->BindAxis("MoveForward", this, &ASonicCharacterComponent::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &ASonicCharacterComponent::MoveRight);
}

void ASonicCharacterComponent::MoveForward(float value)
{
	SonicMovement->MoveForward(value);
}

void ASonicCharacterComponent::MoveRight(float value)
{
	SonicMovement->MoveRight(value);
}

void ASonicCharacterComponent::Jump()
{
	if(SonicMovement->IsFalling())return;
	Super::Jump();
	SonicMovement->Jump();
}

void ASonicCharacterComponent::StopJumping()
{
	Super::StopJumping();
}

void ASonicCharacterComponent::Landed(const FHitResult& Hit)
{
	Super::Landed(Hit);
	SonicMovement->OnLanded();
}

void ASonicCharacterComponent::LaunchSonic(float Speed, FVector Direction, float OutOfControlTime, float KeepVelocityTime,
										   bool bAlignToMovementDirection, float HomingCooldown, bool bCanHomingAttackAfterLaunch,
										   bool bCanAirDashAfterLaunch, bool bMaintainMomentum)
{
	SonicMovement->LaunchSonic(Speed, Direction, OutOfControlTime, KeepVelocityTime, bAlignToMovementDirection, HomingCooldown, bCanHomingAttackAfterLaunch, bCanAirDashAfterLaunch, bMaintainMomentum);
}

void ASonicCharacterComponent::SetLocationAboveActor(AActor* ActorToStandAbove, float HightOffset)
{
	FVector DesiredLocation = ActorToStandAbove->GetActorLocation();
	FVector UpVector = ActorToStandAbove->GetActorUpVector();
	DesiredLocation += UpVector * HightOffset;
	SetActorLocation(DesiredLocation);
}


#pragma once

#include "CoreMinimal.h"
#include "Camera/CameraActor.h"
#include "Components/SplineComponent.h"
#include "UnleashedCamera.generated.h"

UENUM(BlueprintType)
enum class EUnleashedCameraType : uint8
{
	SplineCamera,
	FixedCamera,
	FollowCamera
};

UCLASS()
class SONICUNLEASHEDREMAKE_API AUnleashedCamera : public ACameraActor
{
	GENERATED_BODY()
    
public:
	AUnleashedCamera();

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera Settings")
	EUnleashedCameraType CameraType;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera Settings", meta = (EditCondition = "CameraType == EUnleashedCameraType::Spline"))
	USplineComponent* SplinePath;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera Settings")
	bool bLookAtSonic;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera Settings")
	class ASonicCharacterComponent* SonicCharacter;

	// UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera Settings")
	// UCameraComponent* CameraComponent;


protected:
	virtual void BeginPlay() override;
	virtual void Tick(float DeltaTime) override;

private:
	void UpdateSplineCamera(float DeltaTime);
	void UpdateFollowCamera(float DeltaTime);
	void LookAtTarget(AActor* Target);
};

// Fill out your copyright notice in the Description page of Project Settings.


#include "UnleashedCamera.h"
#include "Sonic/SonicCharacterComponent.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"

AUnleashedCamera::AUnleashedCamera()
{
	PrimaryActorTick.bCanEverTick = true;
	bLookAtSonic = false;
}
void AUnleashedCamera::BeginPlay()
{
	Super::BeginPlay();
	//find sonic
	SonicCharacter = Cast<ASonicCharacterComponent>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
	if (!SonicCharacter)
	{
		UE_LOG(LogTemp, Error, TEXT("SonicCharacter not found in camera"));
	}
}

void AUnleashedCamera::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	if (bLookAtSonic)
	{
		LookAtTarget(SonicCharacter);
	}

	switch(CameraType)
	{
		case EUnleashedCameraType::SplineCamera:
			UpdateSplineCamera(DeltaTime);
			break;
		case EUnleashedCameraType::FollowCamera:
			UpdateFollowCamera(DeltaTime);
			break;
		default:
			break;
	}
}

void AUnleashedCamera::UpdateSplineCamera(float DeltaTime)
{
	//todo
}

void AUnleashedCamera::UpdateFollowCamera(float DeltaTime)
{
	//todo
}

void AUnleashedCamera::LookAtTarget(AActor* Target)
{
	if (Target)
	{
		FRotator LookAtRotation = UKismetMathLibrary::FindLookAtRotation(GetActorLocation(), Target->GetActorLocation());
		SetActorRotation(LookAtRotation);
	}
}

#pragma once

#include "CoreMinimal.h"
#include "UnleashedCamera.h"
#include "Components/ActorComponent.h"
#include "Components/ArrowComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "SonicCameraComponent.generated.h"


UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class SONICUNLEASHEDREMAKE_API USonicCameraComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	USonicCameraComponent();

	// Initializes the camera with the given parameters
	void SetupCamera(AActor* InOwner, USpringArmComponent* InSpringArm, AUnleashedCamera* InCamera);

	// Sets the camera boom length dynamically
	void SetCameraBoomLength(float Distance);

	void ResetCamera(float DeltaTime);

protected:
	virtual void BeginPlay() override;

public:	
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	// Updates the camera each frame based on the owner's movement
	void UpdateCamera(float DeltaTime);

	// --- Camera Settings ---
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.0", UIMin = "0.0"))
	float SpeedThresholdForCameraReset = 100.0f;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.0", UIMin = "0.0"))
	float CameraInterpSpeed = 2.1f;

	// --- Component References ---
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Sonic")
	UArrowComponent* DesiredRotationArrow;

	UPROPERTY()
	USpringArmComponent* SpringArm;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
	AUnleashedCamera* Camera;
	
	UPROPERTY()
	AActor* OwnerActor;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.0", UIMin = "0.0"))
	float CustomDefaultArmLength = DefaultArmLength;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.0", UIMin = "0.0"))
	float CustomCameraLagSpeed = CameraLagSpeedDefault;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera", meta = (ClampMin = "0.0", UIMin = "0.0"))
	float CustomCameraRotationSpeed = CameraRotationSpeedDefault;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Sonic Movement")
	UArrowComponent* CameraDefaultRotation;

	UFUNCTION(BlueprintCallable, Category = "Sonic")
	void SwitchCameraSmooth(AActor* NewViewTarget, float BlendTime = 1.0f, EViewTargetBlendFunction BlendFunc = VTBlend_Cubic, float BlendExp = 1.0f, bool bLockOutgoing = false);

	UFUNCTION(BlueprintCallable, Category = "Sonic")
	AUnleashedCamera* GetCurrentlyActiveCamera();

private:
	// Default Camera Values
	float DefaultArmLength = 300.0f;
	float MaxArmLengthMultiplier = 1.5f;
	float CameraLagSpeedDefault = 30.0f;
	float CameraRotationSpeedDefault = 30.0f;
	float SpeedNormalizationFactor = 1000.0f; 
};

#include "SonicCameraComponent.h"
#include "GameFramework/Character.h"
#include "GameFramework/SpringArmComponent.h"
#include "GameFramework/Controller.h" // Added for clarity 
#include "Sonic/HelperFunctions.h"

USonicCameraComponent::USonicCameraComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
	CameraDefaultRotation = CreateDefaultSubobject<UArrowComponent>(TEXT("CameraDefaultRotation"));
}


void USonicCameraComponent::SetupCamera(AActor* InOwner, USpringArmComponent* InSpringArm, AUnleashedCamera* InCamera)
{
	if (!InOwner || !InSpringArm || !InCamera)
	{
		UE_LOG(LogTemp, Error, TEXT("USonicCameraComponent::SetupCamera - Invalid input parameters"));
		return;
	}
	OwnerActor = InOwner;
	SpringArm = InSpringArm;
	Camera = InCamera;
	if (DesiredRotationArrow && OwnerActor->GetRootComponent()) 
	{
		CameraDefaultRotation->SetupAttachment(OwnerActor->GetRootComponent());
		DesiredRotationArrow->SetupAttachment(OwnerActor->GetRootComponent());
		DesiredRotationArrow->SetVisibility(true);
		DesiredRotationArrow->SetHiddenInGame(false);
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("USonicCameraComponent::SetupCamera - DesiredRotationArrow is invalid"));
	}
	if (SpringArm)
	{
		SpringArm->bEnableCameraLag = true;
		SpringArm->bEnableCameraRotationLag = true;
		SpringArm->CameraLagSpeed = CustomCameraLagSpeed;
		SpringArm->TargetArmLength = CustomDefaultArmLength;
		SpringArm->CameraRotationLagSpeed = CustomCameraRotationSpeed;
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("USonicCameraComponent::SetupCamera - SpringArm is invalid"));
	}
}

void USonicCameraComponent::BeginPlay()
{
	Super::BeginPlay();
}

void USonicCameraComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
	if (!Camera) return;
	FRotator DesiredRotation = OwnerActor->GetActorRotation() + FRotator(-15.0f, 0.0f, 0.0f);
	CameraDefaultRotation->SetWorldRotation(DesiredRotation);
	CameraDefaultRotation->SetWorldLocation(OwnerActor->GetActorLocation());
	UpdateCamera(DeltaTime);
	GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Red, FString::Printf(TEXT("Camera Position: %s"), *Camera->GetActorLocation().ToString()));
}

void USonicCameraComponent::UpdateCamera(float DeltaTime)
{
	if (!OwnerActor || !SpringArm || !Camera) return;

	ACharacter* Character = Cast<ACharacter>(OwnerActor);
	if (!Character) return;

	FVector Velocity = Character->GetVelocity();
	Velocity.Z = 0.0f;
	float Speed = Velocity.Size();
	
	FVector CharacterForward = Character->GetActorForwardVector();
	FVector CameraForward = Camera->GetActorForwardVector();
	
	float DotProduct = FVector::DotProduct(CharacterForward, CameraForward);
	
	const float MarginFacingTowards = 0.85f; // Smaller margin when facing towards the camera
	const float MarginFacingAway = 0.95f;    // Larger margin when facing away from the camera

	// Determine which margin to use
	float EffectiveMargin = DotProduct < 0 ? MarginFacingTowards : MarginFacingAway;

	// Trigger camera reset only if speed is above threshold and Sonic is not facing directly towards or away
	if (Speed > SpeedThresholdForCameraReset && FMath::Abs(DotProduct) < EffectiveMargin)
	{
		ResetCamera(DeltaTime);
	}
}

void USonicCameraComponent::SwitchCameraSmooth(AActor* NewViewTarget, float BlendTime,
	EViewTargetBlendFunction BlendFunc, float BlendExp, bool bLockOutgoing)
{
	APlayerController* PlayerController = Cast<APlayerController>(OwnerActor->GetInstigatorController());
	if (!PlayerController || !NewViewTarget) return;
	PlayerController->SetViewTargetWithBlend(NewViewTarget, BlendTime, BlendFunc, BlendExp, bLockOutgoing);
	
}

AUnleashedCamera* USonicCameraComponent::GetCurrentlyActiveCamera()
{
	return Camera;
}

void USonicCameraComponent::SetCameraBoomLength(float Distance)
{
	if (SpringArm)
	{
		SpringArm->TargetArmLength = Distance;
	}
}

void USonicCameraComponent::ResetCamera(float DeltaTime)
{
	if (!OwnerActor || !SpringArm) return;

	ACharacter* Character = Cast<ACharacter>(OwnerActor);
	if (!Character) return;

	FVector Velocity = Character->GetVelocity();

	if (!Velocity.IsNearlyZero())
	{
		AController* Controller = Character->GetController();
		if (Controller) {
			FRotator ControlRotation = Controller->GetControlRotation();
			ControlRotation.Roll = 0.0f;

			FRotator CameraDesiredRotation = CameraDefaultRotation->GetComponentRotation();
			CameraDesiredRotation.Roll = 0.0f;

			FRotator FinalRotation = FMath::InterpEaseInOut(ControlRotation, CameraDesiredRotation, DeltaTime, CameraInterpSpeed);
			Controller->SetControlRotation(FinalRotation);
		}
	}
}

This code doesnt work, the camera is stuck in 0,0,0. I dont even know if its showing the correct camera, do you know what could be the issue?