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

#pragma once

#include "CoreMinimal.h"
#include "MovementSplineActor.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "SonicMovementComponent.generated.h"

class ASonicCharacterComponent;

/**
 * Custom movement component for Sonic character
 */
UCLASS()
class SONICUNLEASHEDREMAKE_API USonicMovementComponent : public UCharacterMovementComponent
{
    GENERATED_BODY()
    
public:
    USonicMovementComponent();

    void CrouchMovementHandler();
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
    virtual void InitializeComponent() override;
    virtual void BeginPlay() override;
    void OnLanded();

    // Movement functions
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    void MoveForward(float Value);

    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    void MoveRight(float Value);

    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    void Jump();

    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    float GetForwardSpeed() const;
    
    

    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    void LaunchSonic(float Speed, FVector Direction, float OutOfControlTime, float KeepVelocityTime,
        bool bAlignToMovementDirection, float HomingCooldown, bool bCanHomingAttackAfterLaunch = true,
        bool bCanAirDashAfterLaunch = true, bool bMaintainMomentum = false);
    

    // Control functions
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    void GoOutOfControl(float Duration);

    void DoStomp();
    void DoUnleashedStomp();
    void DoGenerationsStomp();
    void DoCrouchedMovement();
    void DoSlide();
    void StopSlide();
    bool HighEnoughToStomp();
    bool IsSonicSliding();
    bool IsSonicCrouching();
    bool IsSonicStomping();
    //crouch stuff
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
        void RightFaceButtonPressed();
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
        void RightFaceButtonReleased();
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
        void EnableStomp();
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
        void DisableStomp();
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
        bool IsStompEnabled();

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement")
    float SplineInfluenceSpeedThreshold;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement")
    float SplineInfluenceRadius;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement")
    float SplineNudgingForce;

    AMovementSplineActor* CurrentSplineActor;
    void HandleSplines();

protected:
    // Helper functions
    bool bCanStomp;
    
    float CalculateLateralMovementScale(float Speed) const;
    void ApplyLateralMovement();
    void DisableCharacterControls();
    void EnableCharacterControls();
    void UpdateCharacterRotation(float DeltaTime);

    // Timer handle
    FTimerHandle OutOfControlTimerHandle;

    // Character reference
    UPROPERTY()
    ASonicCharacterComponent* SonicCharacter;

    // Movement properties
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float MaxBoostSpeed;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float BoostAcceleration;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float LateralMovementScale;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float MinLateralMovementScale;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float MinTurnRadius;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float MaxTurnRadius;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Sonic Movement")
    bool bIsOutOfControl;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float RotationInterpSpeed;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float MinSpeedForMaxTurnRadius;

    // Generations stomp will stop all movement and move you down
    // Unleashed stomp will keep your current momentum so send you down and still move you forward
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stomp", meta = (ClampMin = "0"))
        bool UnleashedStomp;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stomp", meta = (ClampMin = "0"))
        float StompPower;
};

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

#include "Sonic/SonicMovementComponent.h"
#include "Sonic/ESonicCharacterState.h"
#include "Sonic/HelperFunctions.h"
#include "Sonic/SonicCharacterComponent.h"
#include "GameFramework/PlayerController.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"

USonicMovementComponent::USonicMovementComponent()
{
    MaxBoostSpeed = 9000.0f;
    BoostAcceleration = 2000.0f;
    MaxWalkSpeed = 6000.0f;
    MinLateralMovementScale = 0.1f;
    LateralMovementScale = 1.0f;
    bIsOutOfControl = false;
    MinTurnRadius = 10.0f;
    MaxTurnRadius = 100.0f;
    RotationInterpSpeed = 10.0f;
    MinSpeedForMaxTurnRadius = 5000.0f;
    SplineInfluenceSpeedThreshold = 5000.0f;
    SplineInfluenceRadius = 750.0f;
    SplineNudgingForce = 0.2f;
}

void USonicMovementComponent::CrouchMovementHandler()
{
    if (SonicCharacter->CurrentState == EFEeSonicCharacterState::Sliding)
    {
        if (SonicCharacter->GetVelocity().Size() < 1)
        {
            StopSlide();
            return;
        }
        if (IsFalling()) StopSlide();
    }
    if (SonicCharacter->CurrentState == EFEeSonicCharacterState::Crouch)
    {
        if (SonicCharacter->GetVelocity().Size() > 1)
        {
            SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
        }
    }
}

void USonicMovementComponent::InitializeComponent()
{
    Super::InitializeComponent();

    // Initialize Sonic-specific movement parameters
    GravityScale = 4.0f;
    MaxAcceleration = 1000.0f;
    BrakingFrictionFactor = 0.05f;
    SetWalkableFloorAngle(60.0f);
    MaxWalkSpeed = 6000.0f;
    GroundFriction = 8.75f;
    JumpZVelocity = 800.0f;
    AirControl = 0.8f;
    BrakingDecelerationFalling = 0.2f;
    AirControlBoostMultiplier = 1.0f;
    FallingLateralFriction = 4.0f;
    MaxFlySpeed = 7000.0f;
    BrakingDecelerationFlying = 1750.0f;
    MaxCustomMovementSpeed = 9000.0f;
    RotationRate = FRotator(0.0f, 540.0f, 0.0f);
    bCanStomp = true;
    UnleashedStomp = true;
    StompPower = 2000.0f;
}

void USonicMovementComponent::BeginPlay()
{
    Super::BeginPlay();
    SonicCharacter = Cast<ASonicCharacterComponent>(GetOwner());
}


void USonicMovementComponent::OnLanded()
{
    SonicCharacter->BoostComponent->EnableAirBoosting();
    SonicCharacter->HomingAttackComponent->AirDashEnable();
    EnableStomp();
    if (SonicCharacter->CurrentState == EFEeSonicCharacterState::WallJumping || SonicCharacter->CurrentState == EFEeSonicCharacterState::Stomping)
        SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
    
}

void USonicMovementComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    ApplyLateralMovement();
    UpdateCharacterRotation(DeltaTime);
    CrouchMovementHandler();
    HandleSplines();
}

//Calculate the lateral movement scale based on current speed, this will limit the influence of lateral input.
float USonicMovementComponent::CalculateLateralMovementScale(float Speed) const
{
    float MaxSpeed = MaxWalkSpeed;
    float Scale = 1.0f - (Speed / MaxSpeed);
    return FMath::Clamp(Scale, MinLateralMovementScale, 1.0f);
}

void USonicMovementComponent::ApplyLateralMovement()
{
    if (SonicCharacter)
    {
        float CurrentSpeed = SonicCharacter->GetVelocity().Size2D();
        LateralMovementScale = CalculateLateralMovementScale(CurrentSpeed);
    }
}

void USonicMovementComponent::MoveForward(float Value)
{
    if (!SonicCharacter || bIsOutOfControl || !SonicCharacter->bCanMove) return;
    if (IsSonicSliding() || IsSonicCrouching() || IsSonicStomping()) Value = 0;
    const FRotator Rotation = SonicCharacter->SonicCameraComponent->GetCurrentlyActiveCamera()->GetActorRotation();
    const FRotator YawRotation(0, Rotation.Yaw, 0);
    const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
    SonicCharacter->AddMovementInput(Direction, Value);
}

void USonicMovementComponent::MoveRight(float Value)
{
    if (!SonicCharacter || bIsOutOfControl || !SonicCharacter->bCanMove) return;
    if (IsSonicSliding() || IsSonicCrouching()) Value = 0;
    const FRotator Rotation = SonicCharacter->SonicCameraComponent->GetCurrentlyActiveCamera()->GetActorRotation();
    const FRotator YawRotation(0, Rotation.Yaw, 0);
    const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
    SonicCharacter->AddMovementInput(Direction, Value);
}

void USonicMovementComponent::UpdateCharacterRotation(float DeltaTime)
{
    if (!SonicCharacter) return;

    FVector CurrentVelocity = SonicCharacter->GetVelocity();
    float Speed = CurrentVelocity.Size2D();

    if (Speed > 0.1f)
    {
        // Calculate the target rotation based on velocity
        FRotator TargetRotation = CurrentVelocity.Rotation();

        // Calculate the turn radius based on speed
        float SpeedRatio = FMath::Clamp(Speed / MinSpeedForMaxTurnRadius, 0.0f, 1.0f);
        float CurrentTurnRadius = FMath::Lerp(MinTurnRadius, MaxTurnRadius, SpeedRatio);

        // Adjust the rotation rate based on the turn radius
        float CurrentRotationRate = 360.0f / (2 * PI * CurrentTurnRadius);
        RotationRate = FRotator(0.0f, CurrentRotationRate, 0.0f);

        // Interpolate the current rotation to the target rotation
        FRotator NewRotation = FMath::RInterpTo(SonicCharacter->GetActorRotation(), TargetRotation, DeltaTime, RotationInterpSpeed);

        // Set the new rotation
        SonicCharacter->SetActorRotation(NewRotation);
    }
}

void USonicMovementComponent::GoOutOfControl(float Duration)
{
    if (Duration <= 0.0f) return;

    bIsOutOfControl = true; 
    DisableCharacterControls();
    GetWorld()->GetTimerManager().SetTimer(OutOfControlTimerHandle, this, &USonicMovementComponent::EnableCharacterControls, Duration, false);
}

void USonicMovementComponent::DoStomp()
{
    SonicCharacter->CurrentState = EFEeSonicCharacterState::Stomping;
    DisableStomp();
    UnleashedStomp ? DoUnleashedStomp() : DoGenerationsStomp();
}


void USonicMovementComponent::DoUnleashedStomp()
{
    Velocity = SonicCharacter->GetVelocity()/2;
    FVector StompDirection = FVector(0.0f, 0.0f, -1.0f) * StompPower;
    AddImpulse(StompDirection, true);
}

void USonicMovementComponent::DoGenerationsStomp()
{
    Velocity = FVector::ZeroVector;
    FVector StompDirection = FVector(0.0f, 0.0f, -1.0f) * StompPower;
    AddImpulse(StompDirection, true);
}

void USonicMovementComponent::DoCrouchedMovement()
{
    if (!SonicCharacter) return;
    if (SonicCharacter->GetVelocity().Size() > 1)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("DoSlide"));
        DoSlide();
    }
    else
    {
        SonicCharacter->CurrentState = EFEeSonicCharacterState::Crouch;
    }
}

void USonicMovementComponent::DoSlide()
{
    SonicCharacter->CurrentState = EFEeSonicCharacterState::Sliding;
}

void USonicMovementComponent::StopSlide()
{
    SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
}

bool USonicMovementComponent::HighEnoughToStomp()
{
    float searchDistance = 1000.0f;
    float distance = HelperFunctions::GetDistanceFromGround(GetWorld(), SonicCharacter->GetActorLocation(), searchDistance);
    GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("Distance from ground: %f"), distance));
    return distance > 30.0f;
}

bool USonicMovementComponent::IsSonicSliding()
{
    return SonicCharacter->CurrentState == EFEeSonicCharacterState::Sliding;
}

bool USonicMovementComponent::IsSonicCrouching()
{
    return SonicCharacter->CurrentState == EFEeSonicCharacterState::Crouch;
}

bool USonicMovementComponent::IsSonicStomping()
{
    return SonicCharacter->CurrentState == EFEeSonicCharacterState::Stomping;
}


void USonicMovementComponent::RightFaceButtonPressed()
{
    if (!SonicCharacter) return;
    if (IsFalling() && IsStompEnabled() && HighEnoughToStomp())
    {
        DoStomp();
        return;
    }
    if (!IsFalling())
        DoCrouchedMovement();
}

void USonicMovementComponent::RightFaceButtonReleased()
{
    if (!SonicCharacter) return;
    if (SonicCharacter->CurrentState == EFEeSonicCharacterState::Crouch)
    {
        SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
    }
}

void USonicMovementComponent::EnableStomp()
{
    bCanStomp = true;
}

void USonicMovementComponent::DisableStomp()
{
    bCanStomp = false;
}

bool USonicMovementComponent::IsStompEnabled()
{
    return bCanStomp;
}

void USonicMovementComponent::HandleSplines()
{
    UWorld* World = GetWorld();
    if (!World)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("World is null!"));
        return;
    }

    TArray<AActor*> FoundActors;
    UGameplayStatics::GetAllActorsOfClass(World, AMovementSplineActor::StaticClass(), FoundActors);
    FVector PlayerLocation = SonicCharacter->GetActorLocation();
    float ClosestDistance = FLT_MAX;
    FVector DesiredFindLocation = PlayerLocation + (SonicCharacter->GetActorForwardVector() * 200.0f);
    if (FoundActors.Num() == 0)
    {
        CurrentSplineActor = nullptr;
        return;
    }
    for (AActor* FoundActor : FoundActors)
    {
        AMovementSplineActor* SplineActor = Cast<AMovementSplineActor>(FoundActor);
        if (SplineActor)
        {
            FVector SplineLocation = SplineActor->SplineComponent->FindLocationClosestToWorldLocation(DesiredFindLocation, ESplineCoordinateSpace::World);
            float Distance = FVector::Dist(PlayerLocation, SplineLocation);
            if (Distance < ClosestDistance)
            {
                ClosestDistance = Distance;
                CurrentSplineActor = SplineActor;
            }
        }
    }
    // we need to get the forward input to check if the user is holding the stick
    float forwardInput = SonicCharacter->GetInputAxisValue("MoveForward");
    float rightInput = SonicCharacter->GetInputAxisValue("MoveRight");
    float totalInput = FMath::Abs(forwardInput) + FMath::Abs(rightInput);
    
    if (totalInput < 0.1f && !SonicCharacter->BoostComponent->IsBoosting())
    {
        CurrentSplineActor = nullptr;
    }
    if (CurrentSplineActor && SonicCharacter->CurrentState == EFEeSonicCharacterState::MovingOnGround)
    {
        float ForwardDistance = 200.0f;
        FVector ForwardVector = SonicCharacter->GetActorForwardVector() * ForwardDistance;
        FVector DesiredLocation = PlayerLocation + ForwardVector;
        FVector Location = CurrentSplineActor->SplineComponent->FindLocationClosestToWorldLocation(DesiredLocation, ESplineCoordinateSpace::World);
        FRotator DesiredRotation = (Location - PlayerLocation).GetSafeNormal().Rotation();
        DesiredRotation.Pitch = 0.0f;
        SonicCharacter->FancyArrow->SetWorldRotation(DesiredRotation);

        float DotProduct = FVector::DotProduct(SonicCharacter->GetActorForwardVector(), (Location - PlayerLocation).GetSafeNormal());
        if (DotProduct < 0.0f)
        {
            CurrentSplineActor = nullptr;
            return;
        }
        FVector DesiredDirection = (Location - PlayerLocation).GetSafeNormal();
        float DistanceToSpline = FVector::Dist(SonicCharacter->GetActorLocation(), Location);
        float SpeedInfluence = FMath::Clamp(SonicCharacter->GetVelocity().Size() / SplineInfluenceSpeedThreshold, 0.0f, 1.0f );
        float SplineWeight = FMath::Clamp((1.0f - DistanceToSpline / SplineInfluenceRadius) * SpeedInfluence, 0.0f, 1.0f);

        FVector MovementDirection = SonicCharacter->GetActorForwardVector();
        FVector BlendedDirection = FMath::Lerp(MovementDirection, DesiredDirection, SplineWeight).GetSafeNormal();
        
        SonicCharacter->AddMovementInput(BlendedDirection, SplineNudgingForce * SplineWeight);
        
    }
}

void USonicMovementComponent::DisableCharacterControls()
{
    if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
    {
        SonicCharacter->DisableInput(PlayerController);
    }
}

void USonicMovementComponent::EnableCharacterControls()
{
    if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
    {
        SonicCharacter->EnableInput(PlayerController);
    }
    bIsOutOfControl = false;
}

void USonicMovementComponent::Jump()
{
    if (!SonicCharacter) return;
    const FVector JumpDirection = SonicCharacter->GetActorUpVector() * JumpZVelocity;
    AddImpulse(JumpDirection, true);
    SonicCharacter->CurrentState = EFEeSonicCharacterState::Jumping;
}

float USonicMovementComponent::GetForwardSpeed() const
{
    FVector Velo = Velocity;
    Velo.Z = 0.0f;
    return Velo.Size();
}

void USonicMovementComponent::LaunchSonic(float Speed, FVector Direction, float OutOfControlTime,
                                          float KeepVelocityTime, bool bAlignToMovementDirection, float HomingCooldown, 
                                          bool bCanHomingAttackAfterLaunch, bool bCanAirDashAfterLaunch, bool bMaintainMomentum)
{
    if (!SonicCharacter)
    {
        UE_LOG(LogTemp, Error, TEXT("SonicMovementComponent: SonicCharacter is not set!"));
        return;
    }

    const FVector LaunchVelocity = Direction * Speed;
    if (LaunchVelocity.Z > 0.0f)
    {
        SetMovementMode(MOVE_Falling);
        SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
    }

    if (!bMaintainMomentum)
    {
        Velocity = FVector::ZeroVector;
    }
    Velocity += LaunchVelocity;

    if (bAlignToMovementDirection)
    {
        FRotator LaunchRotation = LaunchVelocity.Rotation();
        LaunchRotation.Pitch = SonicCharacter->GetActorRotation().Pitch;
        LaunchRotation.Roll = SonicCharacter->GetActorRotation().Roll;
        SonicCharacter->SetActorRotation(LaunchRotation);
    }

    // Handle component-specific logic
    if (bCanAirDashAfterLaunch)
    {
        SonicCharacter->HomingAttackComponent->AirDashEnable();
    }
    if (bCanHomingAttackAfterLaunch)
    {
        SonicCharacter->HomingAttackComponent->HomingAttackEnable();
    }
    if (HomingCooldown > 0.0f)
    {
        SonicCharacter->HomingAttackComponent->HomingAttackCooldownEnable(HomingCooldown);
    }
    if (OutOfControlTime > 0.0f)
    {
        GoOutOfControl(OutOfControlTime);
    }

    // Debug message
    GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("LaunchSonic: Speed=%f, Direction=(%f, %f, %f)"), Speed, Direction.X, Direction.Y, Direction.Z));
}


Right now sonic can only run on the floor, but i want it so, once you run fast enough, (around 500 units) you can start running on walls and if you maintain your speed you can keep running on the walls.
also i want sonics mesh to correctly rotate based on if hes running on the wall and if hes on sloped floors ect, can you think you can properly implement this. if so, do it