// RadialMenuController.cpp

#include "RadialMenuController.h"
#include "RadialMenuWidget.h"
#include "GameFramework/PlayerController.h"
#include "Kismet/GameplayStatics.h"
#include "Blueprint/WidgetLayoutLibrary.h"

URadialMenuController::URadialMenuController()
{
    // We only tick while the menu is open; we toggle this in Open/Close so the
    // component costs nothing the rest of the time.
    PrimaryComponentTick.bCanEverTick = true;
    PrimaryComponentTick.bStartWithTickEnabled = false;
}

void URadialMenuController::OpenMenu()
{
    if (bIsOpen || Entries.Num() == 0)
    {
        return;
    }

    APlayerController* PC = Cast<APlayerController>(
        UGameplayStatics::GetPlayerController(this, 0));
    if (!PC || !WidgetClass)
    {
        return;
    }

    bIsOpen = true;
    HoveredIndex = -1;
    OpenRealTimeSeconds = FApp::GetCurrentTime(); // undilated wall-ish time

    // Dilate time. CustomTimeDilation on the actor would only affect us; we want
    // the whole world to slow, so we use global dilation and cache the old value.
    CachedTimeDilation = UGameplayStatics::GetGlobalTimeDilation(this);
    SetTimeDilation(OpenTimeDilation);

    // Show + center the cursor so the player has a clear anchor to aim from.
    PC->bShowMouseCursor = true;
    int32 ViewX, ViewY;
    PC->GetViewportSize(ViewX, ViewY);
    PC->SetMouseLocation(ViewX / 2, ViewY / 2);

    ActiveWidget = CreateWidget<URadialMenuWidget>(PC, WidgetClass);
    if (ActiveWidget)
    {
        ActiveWidget->InitializeFromController(this, Entries);

        // CommonUI activatable widgets belong on an activatable stack/layer so
        // the Back action and activation lifecycle work. If you have a layer set
        // up, push there instead of the viewport (see PushToLayer below). For a
        // standalone setup we add to viewport and activate manually.
        ActiveWidget->AddToViewport(100); // high Z so it sits above the HUD
        ActiveWidget->ActivateWidget();    // triggers NativeOnActivated -> binds input
    }

    SetComponentTickEnabled(true);
}

void URadialMenuController::CloseAndConfirm()
{
    if (!bIsOpen)
    {
        return;
    }

    // Tap-to-open guard: if a confirm arrives within the guard window of opening,
    // treat it as noise from the opening press rather than a real selection.
    // We swallow it entirely (menu stays open) so the player can still choose.
    if (OpenInputGuardSeconds > 0.f
        && (FApp::GetCurrentTime() - OpenRealTimeSeconds) < OpenInputGuardSeconds)
    {
        return;
    }

    const int32 Confirmed =
        (HoveredIndex >= 0 && Entries.IsValidIndex(HoveredIndex)
         && Entries[HoveredIndex].bEnabled)
            ? HoveredIndex
            : -1;

    // Tear down first so listeners see a clean closed state when they react.
    const int32 IndexToBroadcast = Confirmed;
    CloseAndCancel();
    OnSelectionConfirmed.Broadcast(IndexToBroadcast);
}

void URadialMenuController::CloseAndCancel()
{
    if (!bIsOpen)
    {
        return;
    }

    bIsOpen = false;
    SetComponentTickEnabled(false);
    SetTimeDilation(CachedTimeDilation);

    if (APlayerController* PC = Cast<APlayerController>(
            UGameplayStatics::GetPlayerController(this, 0)))
    {
        PC->bShowMouseCursor = false;
    }

    if (ActiveWidget)
    {
        // DeactivateWidget triggers NativeOnDeactivated, which unregisters the
        // Confirm/Back bindings. Then remove it from the viewport/layer.
        ActiveWidget->DeactivateWidget();
        ActiveWidget->RemoveFromParent();
        ActiveWidget = nullptr;
    }

    HoveredIndex = -1;
}

void URadialMenuController::TickComponent(float DeltaTime, ELevelTick TickType,
    FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    if (bIsOpen)
    {
        UpdateHoverFromInput();
    }
}

void URadialMenuController::UpdateHoverFromInput()
{
    APlayerController* PC = Cast<APlayerController>(
        UGameplayStatics::GetPlayerController(this, 0));
    if (!PC)
    {
        return;
    }

    // --- Gamepad right stick takes priority if it's pushed past the dead zone --
    float StickX = 0.f, StickY = 0.f;
    PC->GetInputAnalogStickState(EControllerAnalogStick::CAS_RightStick, StickX, StickY);
    const float StickMag = FVector2D(StickX, StickY).Size();

    float DirX, DirY;
    bool bHaveDirection = false;

    if (StickMag >= SelectionDeadZone)
    {
        DirX = StickX;
        DirY = StickY;
        bHaveDirection = true;
    }
    else
    {
        // --- Fall back to mouse: direction from screen center to cursor -------
        float MouseX, MouseY;
        if (PC->GetMousePosition(MouseX, MouseY))
        {
            int32 ViewX, ViewY;
            PC->GetViewportSize(ViewX, ViewY);
            const FVector2D Center(ViewX * 0.5f, ViewY * 0.5f);
            const FVector2D Offset = FVector2D(MouseX, MouseY) - Center;

            // Dead zone in pixels scaled to viewport so it's resolution-stable.
            const float DeadPx = SelectionDeadZone * (ViewY * 0.25f);
            if (Offset.Size() >= DeadPx)
            {
                DirX = Offset.X;
                DirY = -Offset.Y; // screen Y is down; flip so up = +Y
                bHaveDirection = true;
            }
            else
            {
                DirX = DirY = 0.f;
            }
        }
        else
        {
            DirX = DirY = 0.f;
        }
    }

    if (!bHaveDirection)
    {
        // Inside dead zone: keep whatever was hovered (no jitter, no deselect).
        return;
    }

    UpdateHoverFromDirection(DirX, DirY, FVector2D(DirX, DirY).Size());
}

void URadialMenuController::UpdateHoverFromDirection(float DirX, float DirY, float Magnitude)
{
    if (!bIsOpen)
    {
        return;
    }

    // Below the dead zone we keep the current hover rather than deselecting,
    // so releasing the stick to neutral doesn't clear the player's choice.
    if (Magnitude < SelectionDeadZone)
    {
        return;
    }

    // Angle clockwise from straight up (12 o'clock), in [0,360).
    // atan2(x, y) gives angle from +Y axis turning toward +X = clockwise.
    float AngleDeg = FMath::RadiansToDegrees(FMath::Atan2(DirX, DirY));
    if (AngleDeg < 0.f)
    {
        AngleDeg += 360.f;
    }

    const int32 NewIndex = AngleToSegment(AngleDeg);
    if (NewIndex != HoveredIndex)
    {
        HoveredIndex = NewIndex;
        if (ActiveWidget)
        {
            ActiveWidget->SetHoveredSegment(HoveredIndex);
        }
    }
}

int32 URadialMenuController::AngleToSegment(float AngleDegrees) const
{
    const int32 Count = Entries.Num();
    if (Count <= 0)
    {
        return -1;
    }

    const float SliceSize = 360.f / Count;

    // Offset by half a slice so that slice 0 is *centered* on 12 o'clock
    // rather than starting there — matches the GTA layout.
    const float Shifted = FMath::Fmod(AngleDegrees + SliceSize * 0.5f, 360.f);
    return FMath::Clamp(FMath::FloorToInt(Shifted / SliceSize), 0, Count - 1);
}

void URadialMenuController::SetTimeDilation(float Scale)
{
    UGameplayStatics::SetGlobalTimeDilation(this, Scale);
}