//======================================================================================================
// Company Name:       MotionTechnologies.inc
// Copyright Year:     2025
// File:               MoARemapAssetBase.h
//======================================================================================================

#pragma once

#include "CoreMinimal.h"
#include "LiveLinkRemapAsset.h"
#include "Animation/Skeleton.h"
#include "MoABoneMapping.h"
#include "MoABoneNameMappings.h"
#include "Animation/AnimSequence.h"
#include "MoARemapAssetBase.generated.h"

UCLASS(Abstract, Blueprintable, EditInlineNew)
class MOALIVELINK_API UMoARemapAssetBase : public ULiveLinkRemapAsset
{
	GENERATED_BODY()

public:
    UMoARemapAssetBase();

    virtual void BuildPoseFromAnimationData(float DeltaTime, const FLiveLinkSkeletonStaticData* InSkeletonData,
        const FLiveLinkAnimationFrameData* InFrameData,
        FCompactPose& OutPose) override;

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

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Live Link", meta = (DisplayName = "Target Skeleton"))
    TObjectPtr<USkeleton> TargetSkeleton;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Live Link", meta = (DisplayName = "Ground Offset (cm)", ClampMin = "-100.0", ClampMax = "100.0", UIMin = "-100.0", UIMax = "100.0"))
    float GroundOffset = 0.0f;

    // When enabled, keeps original XY scale (1.0) so multiple actors maintain real-world relative positions
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Motion Processing", meta = (DisplayName = "Preserve Actor Distances"))
    bool bPreserveActorDistances = false;

    // When enabled, applies floor calibration to make mesh bottom touch ground (Z=0)
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Motion Processing", meta = (DisplayName = "Use Hip Correction"))
    bool bUseHipCorrection = true;

    UPROPERTY()
    TObjectPtr<USkeleton> TPoseReferenceSkeleton = nullptr;

    UPROPERTY(VisibleAnywhere, Category = "Bone Mapping Status", meta = (DisplayName = "Duplicate Check"))
    FString DuplicateStatusText;

    UPROPERTY(VisibleAnywhere, Category = "Bone Mapping Status", meta = (DisplayName = "Missing Check"))
    FString MissingStatusText;

    UPROPERTY(EditAnywhere, Category = "Bone Mapping", meta = (ShowOnlyInnerProperties, DisplayName = "Body & Spine"))
    TArray<FMoABoneMappingRow> BodySpineBones;

    UPROPERTY(EditAnywhere, Category = "Bone Mapping", meta = (ShowOnlyInnerProperties, DisplayName = "Legs"))
    TArray<FMoABoneMappingRow> LegBones;

    UPROPERTY(EditAnywhere, Category = "Bone Mapping", meta = (ShowOnlyInnerProperties, DisplayName = "Arms"))
    TArray<FMoABoneMappingRow> ArmBones;

    UPROPERTY(EditAnywhere, Category = "Bone Mapping", meta = (ShowOnlyInnerProperties, DisplayName = "Fingers"))
    TArray<FMoABoneMappingRow> FingerBones;

    void AutoMapFromTargetSkeleton();

    void ClearBoneMapping();

    const TMap<FName, TArray<FName>>& GetDuplicateTargetBones() const { return DuplicateTargetBones; }
    
    void UpdateDuplicateDetection();

private:
    void InitializeAllBones();
    
    TArray<FMoABoneMappingRow> GetAllBones() const;
    
    FName FindBestMatchWithHierarchyValidation(
        FName SourceBoneName,
        const TMap<FString, FName>& NormalizedNameMap,
        const TMap<FString, TArray<FName>>& NormalizedToAllTargets,
        const TMap<FName, FName>& TargetBoneParentMap,
        const TMap<FString, FName>& CurrentMappings,
        const TMap<FString, FMoABoneHierarchyInfo>& BoneHierarchy) const;

    FName* FindBestMatchWithHierarchyValidationInternal(
        FName SourceBoneName,
        const TMap<FString, FName>& NormalizedNameMap,
        const TMap<FString, TArray<FName>>& NormalizedToAllTargets,
        const TMap<FName, FName>& TargetBoneParentMap,
        const TMap<FString, FName>& CurrentMappings,
        const TMap<FString, FMoABoneHierarchyInfo>& BoneHierarchy);
        
protected:
    virtual FVector ConvertRootPosition(FVector LLPosition) const { return LLPosition; };
    virtual FQuat ConvertRootRotation(FQuat LLRotation) const { return LLRotation; };
    virtual FQuat ConvertBoneRotation(FQuat LLRotation) const { return LLRotation; };
    virtual FVector ConvertBonePosition(FVector LLPosition) const { return LLPosition; }
    virtual FName GetTargetRootName() const { return "root"; }
    
    virtual FName GetRemappedBoneName(FName BoneName) const { return BoneName; }

    TMap<FName, FName> BoneNameMap;
    
    TMap<FName, TArray<FName>> DuplicateTargetBones;

    TMap<FName, FQuat> TPoseRotationDeltaMap;

    FName CachedTPoseSkeletonName = NAME_None;

    float CachedMoAUpperLegLength = 0.0f;
    float CachedMoALowerLegLength = 0.0f;
    float CachedMoAHipToUpperLegLength = 0.0f;

    float CachedUnrealUpperLegLength = 0.0f;
    float CachedUnrealLowerLegLength = 0.0f;
    float CachedUnrealHipToUpperLegLength = 0.0f;

    FVector CachedPositionScale = FVector(1.0f, 1.0f, 1.0f);

    bool bLegLengthCacheValid = false;

    // Height correction: Make mesh bottom touch ground (Z=0)
    float CachedTargetHipToAnkle = 0.0f;         // T-Pose Hip.Z - Ankle.Z
    float CachedTargetAnkleToMeshBottom = 0.0f;  // T-Pose Ankle.Z - MeshBottom.Z (shoe thickness)
    float CachedMeshFloorOffset = 0.0f;          // Offset to make mesh bottom = 0
    bool bHeightCacheValid = false;
    bool bMeshFloorCalibrated = false;

    void UpdateTPoseRotationDeltas(const FBoneContainer& BoneContainer);
    void CalculateTPoseDeltasFromReferenceSkeleton();
    void CalculateHipHeightOffset();
};
