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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/BoxComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/BillboardComponent.h"

#include "MapCheckpointTrigger.generated.h"

class UShapeComponent;

// Mirrors ECheckpointType from the game's Script/Data/Enums.as. That enum is
// AngelScript-defined (/Script/Angelscript.ECheckpointType) and therefore cannot exist
// in a vanilla-UE mapping project, so the bridge declares its own. The game-side swap
// maps between the two by integer value -- keep the order identical.
UENUM(BlueprintType)
enum class EMapCheckpointType : uint8
{
    Classic UMETA(DisplayName = "Classic"),
    Start UMETA(DisplayName = "Start")
};

/**
 * Mapper-facing proxy for the game's checkpoint trigger.
 *
 * This is NOT the game's ACheckpointTrigger. That class is AngelScript
 * (/Script/Angelscript.CheckpointTrigger) and its components live in
 * BP_Checkpoint_Trigger, so neither is reachable from a vanilla-UE mapping project.
 *
 * Place this actor instead. It carries data and an authoring volume only -- no gameplay.
 * The one thing it does do at runtime is provide its own APlayerStart when Type is Start,
 * so a map needs no separate PlayerStart actor; see PostInitializeComponents below.
 * At map load the shipped game swaps it for the real BP_Checkpoint_Trigger at this same
 * transform, copies these properties across, and destroys the proxy. See Docs/SYNC.md
 * for the swap contract, and the game's Docs/CodeNotes.md [MODPROXY-01] for the rationale.
 *
 * Properties are matched by name, so renaming one silently drops the mapper's authored
 * value. Components are matched by an explicit name pair held game-side in
 * UMapProxySwapSubsystem, so renaming a component needs a matching change there.
 */
UCLASS(Blueprintable, meta = (DisplayName = "Checkpoint Trigger"))
class VHOLUME_API AMapCheckpointTrigger : public AActor
{
    GENERATED_BODY()

public:
    AMapCheckpointTrigger();

    // Root component, and the respawn pose. In the game, SpawnPoint is the root of
    // BP_Checkpoint_Trigger so its world transform equals the actor transform -- the
    // player is respawned here facing this rotation.
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Checkpoint")
    UCapsuleComponent *SpawnPoint;

    // The overlap volume that activates the checkpoint. Size this to cover the route.
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Checkpoint")
    UBoxComponent *Box;

    // Editor-only icon to make the actor easier to select.
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Checkpoint")
    UBillboardComponent *Billboard;

    // Every map needs exactly one Start checkpoint -- it is the spawn point and the
    // timer origin. All others are Classic.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Checkpoint",
              meta = (ToolTip = "Start is the spawn point and timer origin. Place exactly one per map; all others are Classic."))
    EMapCheckpointType Type;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Checkpoint",
              meta = (ToolTip = "Allow this checkpoint to be triggered more than once per run (lap-style maps)"))
    bool bAllowMultipleTriggers;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Checkpoint",
              meta = (ToolTip = "Crossing this checkpoint completes the lap, provided every other checkpoint has been used"))
    bool bIsFinalForLap;

    // Deliberately NOT mirrored from the game's ACheckpointTrigger:
    //   CheckpointId       - regenerated by BP_Checkpoint_Trigger's ConstructionScript
    //                        from the spawn transform, so the swap produces it for free.
    //   bWasUsedForLap     - runtime state, not authored.
    //   BillboardIconsData - UBillboardIconsData is a game-only UDataAsset.
    //   ActorEnablerCenter / ActorEnablerConapt - AActorEnabler is game-only, and both
    //                        drive base-game progression that mod maps must not touch.

#if WITH_EDITOR
    virtual void PostEditChangeProperty(FPropertyChangedEvent &PropertyChangedEvent) override;

    // Map Check validation. A map with no Start checkpoint spawns the player wherever the
    // engine feels like and leaves the whole checkpoint system inert, which is expensive to
    // diagnose at playtest and trivial to catch here.
    virtual void CheckForErrors() override;
#endif

    virtual void OnConstruction(const FTransform &Transform) override;

    // Deliberately does NOT provide its own APlayerStart.
    //
    // That was tried: spawning one from PostInitializeComponents, which runs inside
    // UWorld::InitializeActorsForPlay -> ULevel::RouteActorInitialize and therefore before
    // SpawnPlayActor and ChoosePlayerStart. The timing was right and it still did not fix
    // the freeze on a map with no level-baked PlayerStart, while adding one by hand does.
    // Something needs a PlayerStart present earlier than that, or needs it baked into the
    // level rather than spawned; the cause is not yet understood.
    //
    // So a mod map MUST contain a real APlayerStart. The shipped template carries one named
    // "PlayerStart-DoNotRemove", and VHMap's diagnostics enforce it. See Docs/STATUS.md.

private:
    // Same collision configuration AWindZone applies to its trigger shapes, so mappers
    // get a volume that behaves identically in their editor.
    void ApplyCollisionSetup(UShapeComponent *ShapeComp) const;

    // Start and Classic checkpoints do completely different jobs, so they must not look
    // identical in the viewport. The game does the equivalent via BillboardIconsData; that
    // asset is game-only, so the bridge picks between two engine sprites instead.
    void UpdateBillboardSprite();

    UPROPERTY(Transient)
    UTexture2D *StartSprite;

    UPROPERTY(Transient)
    UTexture2D *ClassicSprite;
};
