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

#include "MoARemapAssetBase.h"
#include "Roles/LiveLinkAnimationTypes.h"
#include "BonePose.h"
#include "MoABoneMapping.h"
#include "MoABoneNameMappings.h"
#include "MoATposeCalculator.h"
#include "UObject/ConstructorHelpers.h"
#include "DrawDebugHelpers.h"
#include "Engine/World.h"
#include "Engine/Engine.h"
#include "Rendering/SkeletalMeshRenderData.h"

#if WITH_EDITOR
#include "Editor.h"
#endif

UMoARemapAssetBase::UMoARemapAssetBase()
{
    InitializeAllBones();

    // Auto-load T-Pose Reference Skeleton from plugin content
    static ConstructorHelpers::FObjectFinder<USkeleton> TPoseSkeletonFinder(
        TEXT("/MoALiveLink/MoA_TPose_Skeleton.MoA_TPose_Skeleton"));
    if (TPoseSkeletonFinder.Succeeded())
    {
        TPoseReferenceSkeleton = TPoseSkeletonFinder.Object;
    }
}

void UMoARemapAssetBase::InitializeAllBones()
{
    BodySpineBones.Empty();
    LegBones.Empty();
    ArmBones.Empty();
    FingerBones.Empty();

    for (int32 i = 0; i < (int32)EMoABoneName::Count; i++)
    {
        EMoABoneName BoneEnum = (EMoABoneName)i;
        FMoABoneMappingRow Row(BoneEnum, NAME_None);

        EMoABoneCategory Category = FMoABoneNameUtils::GetBoneCategory(BoneEnum);

        switch (Category)
        {
            case EMoABoneCategory::BodyAndSpine:
                BodySpineBones.Add(Row);
                break;
            case EMoABoneCategory::Legs:
                LegBones.Add(Row);
                break;
            case EMoABoneCategory::Arms:
                ArmBones.Add(Row);
                break;
            case EMoABoneCategory::Fingers:
                FingerBones.Add(Row);
                break;
        }
    }

}

TArray<FMoABoneMappingRow> UMoARemapAssetBase::GetAllBones() const
{
    TArray<FMoABoneMappingRow> AllBones;
    AllBones.Append(BodySpineBones);
    AllBones.Append(LegBones);
    AllBones.Append(ArmBones);
    AllBones.Append(FingerBones);
    return AllBones;
}

#if WITH_EDITOR
void UMoARemapAssetBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
    Super::PostEditChangeProperty(PropertyChangedEvent);
    
    if (PropertyChangedEvent.MemberProperty != nullptr)
    {
        FName MemberPropertyName = PropertyChangedEvent.MemberProperty->GetFName();
        
        if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, TargetSkeleton))
        {
            if (TargetSkeleton)
            {
                AutoMapFromTargetSkeleton();
            }
            else
            {
                UE_LOG(LogTemp, Warning, TEXT("[MoA Remap] Target Skeleton cleared."));
            }
            TPoseRotationDeltaMap.Empty();
            CachedTPoseSkeletonName = NAME_None;

            // Reset leg length cache when skeleton changes
            bLegLengthCacheValid = false;
            
            // Reset height correction cache
            bHeightCacheValid = false;
            CachedTargetHipToAnkle = 0.0f;
            CachedTargetAnkleToMeshBottom = 0.0f;
            CachedMeshFloorOffset = 0.0f;
            bMeshFloorCalibrated = false;
        }
        
        if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, TPoseReferenceSkeleton))
        {
            TPoseRotationDeltaMap.Empty();
            CachedTPoseSkeletonName = NAME_None;
        }

        // Reset calibration when motion processing options change
        if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, bPreserveActorDistances) ||
            MemberPropertyName == GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, bUseHipCorrection))
        {
            bLegLengthCacheValid = false;
            bMeshFloorCalibrated = false;
        }

        if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, BodySpineBones) ||
            MemberPropertyName == GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, LegBones) ||
            MemberPropertyName == GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, ArmBones) ||
            MemberPropertyName == GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, FingerBones))
        {
            if (PropertyChangedEvent.Property != nullptr)
            {
                FName PropertyName = PropertyChangedEvent.Property->GetFName();
                if (PropertyName == GET_MEMBER_NAME_CHECKED(FMoABoneMappingRow, TargetBone))
                {
                    UpdateDuplicateDetection();
                }
            }
        }
    }
}
#endif

void UMoARemapAssetBase::ClearBoneMapping()
{
#if WITH_EDITOR
    Modify();
#endif

    InitializeAllBones();

    BoneNameMap.Empty();
    DuplicateTargetBones.Empty();
    DuplicateStatusText = TEXT("OK");

    int32 TotalBones = (int32)EMoABoneName::Count;
    MissingStatusText = FString::Printf(TEXT("%d unmapped"), TotalBones);

    // Reset leg length cache
    bLegLengthCacheValid = false;
    
    // Reset height correction cache
    bHeightCacheValid = false;
    CachedTargetHipToAnkle = 0.0f;
    CachedTargetAnkleToMeshBottom = 0.0f;
    CachedMeshFloorOffset = 0.0f;
    bMeshFloorCalibrated = false;
}

void UMoARemapAssetBase::UpdateDuplicateDetection()
{
    DuplicateTargetBones.Empty();

    TMap<FName, TArray<FName>> TargetBoneUsage; // TargetBone -> SourceBones that map to it
    TMap<FName, TSet<FString>> TargetBoneGroups; // TargetBone -> Groups it appears in
    int32 TotalMappedCount = 0;

    struct FBoneArrayInfo
    {
        TArray<FMoABoneMappingRow>* Array;
        FString GroupName;
    };

    FBoneArrayInfo AllArrays[] = {
        { &BodySpineBones, TEXT("Body/Spine") },
        { &LegBones, TEXT("Leg") },
        { &ArmBones, TEXT("Arm") },
        { &FingerBones, TEXT("Finger") }
    };
    
    for (const FBoneArrayInfo& ArrayInfo : AllArrays)
    {
        if (!ArrayInfo.Array)
        {
            UE_LOG(LogTemp, Error, TEXT("[MoA Remap] Null BoneArray detected in UpdateDuplicateDetection!"));
            continue;
        }
        
        for (const FMoABoneMappingRow& Row : *ArrayInfo.Array)
        {
            if (Row.TargetBone != NAME_None && !Row.TargetBone.IsNone())
            {
                FString TargetBoneStr = Row.TargetBone.ToString();
                TargetBoneStr.TrimStartAndEndInline();
                
                if (TargetBoneStr.IsEmpty())
                {
                    continue;
                }
                
                FName SourceBoneName = FMoABoneNameUtils::EnumToFName(Row.SourceBone);
                
                if (SourceBoneName == NAME_None || SourceBoneName.IsNone())
                {
                    continue;
                }
                
                TotalMappedCount++;
                
                if (!TargetBoneUsage.Contains(Row.TargetBone))
                {
                    TargetBoneUsage.Add(Row.TargetBone, TArray<FName>());
                }
                TargetBoneUsage[Row.TargetBone].Add(SourceBoneName);
                
                if (!TargetBoneGroups.Contains(Row.TargetBone))
                {
                    TargetBoneGroups.Add(Row.TargetBone, TSet<FString>());
                }
                TargetBoneGroups[Row.TargetBone].Add(ArrayInfo.GroupName);
            }
        }
    }

    TSet<FString> AffectedGroups;
    for (const auto& Pair : TargetBoneUsage)
    {
        if (Pair.Value.Num() > 1)
        {
            DuplicateTargetBones.Add(Pair.Key, Pair.Value);

            if (TargetBoneGroups.Contains(Pair.Key))
            {
                for (const FString& Group : TargetBoneGroups[Pair.Key])
                {
                    AffectedGroups.Add(Group);
                }
            }

            FString SourceList;
            for (int32 i = 0; i < Pair.Value.Num(); ++i)
            {
                if (i > 0) SourceList += TEXT(", ");
                SourceList += Pair.Value[i].ToString();
            }

            UE_LOG(LogTemp, Warning, TEXT("[MoA Remap] Duplicate detected: '%s' <- [%s]"),
                *Pair.Key.ToString(), *SourceList);
        }
    }

    if (DuplicateTargetBones.Num() > 0)
    {
        FString GroupList;
        int32 GroupIndex = 0;
        for (const FString& Group : AffectedGroups)
        {
            if (GroupIndex > 0) GroupList += TEXT(", ");
            GroupList += Group;
            GroupIndex++;
        }

        DuplicateStatusText = FString::Printf(TEXT("%d duplicate(s) [%s]"), DuplicateTargetBones.Num(), *GroupList);

        UE_LOG(LogTemp, Warning, TEXT("[MoA Remap] Duplicate mappings detected: %d target bones have multiple source bones in groups: %s"),
            DuplicateTargetBones.Num(), *GroupList);
    }
    else
    {
        DuplicateStatusText = TEXT("OK");
    }

    TArray<FName> MissingBones;
    TMap<FString, TArray<FName>> MissingByGroup;
    
    for (const FBoneArrayInfo& ArrayInfo : AllArrays)
    {
        if (!ArrayInfo.Array) continue;
        
        for (const FMoABoneMappingRow& Row : *ArrayInfo.Array)
        {
            if (Row.TargetBone == NAME_None || Row.TargetBone.IsNone())
            {
                FName SourceBoneName = FMoABoneNameUtils::EnumToFName(Row.SourceBone);
                if (SourceBoneName != NAME_None)
                {
                    MissingBones.Add(SourceBoneName);
                    
                    if (!MissingByGroup.Contains(ArrayInfo.GroupName))
                    {
                        MissingByGroup.Add(ArrayInfo.GroupName, TArray<FName>());
                    }
                    MissingByGroup[ArrayInfo.GroupName].Add(SourceBoneName);
                }
            }
        }
    }

    if (MissingBones.Num() > 0)
    {
        FString GroupSummary;
        for (const auto& Pair : MissingByGroup)
        {
            if (!GroupSummary.IsEmpty()) GroupSummary += TEXT(", ");
            GroupSummary += FString::Printf(TEXT("%s:%d"), *Pair.Key, Pair.Value.Num());
        }

        MissingStatusText = FString::Printf(TEXT("%d unmapped [%s]"), MissingBones.Num(), *GroupSummary);
    }
    else
    {
        MissingStatusText = TEXT("OK");
    }
}

void UMoARemapAssetBase::AutoMapFromTargetSkeleton()
{
    if (!TargetSkeleton)
    {
        UE_LOG(LogTemp, Warning, TEXT("[MoA Remap] Target Skeleton is not assigned!"));
        return;
    }

#if WITH_EDITOR
    Modify();
#endif

    InitializeAllBones();

    // Get reference skeleton from target skeleton
    const FReferenceSkeleton& RefSkeleton = TargetSkeleton->GetReferenceSkeleton();
    
    TMap<FString, FName> NormalizedNameMap;  // Normalized bone name -> Original FName
    TMap<FName, FName> TargetBoneParentMap;  // Target bone -> Parent bone (for hierarchy validation)
    TMap<FString, TArray<FName>> NormalizedToAllTargets; // Normalized name -> All matching target bones
    
    NormalizedNameMap.Reserve(RefSkeleton.GetNum());
    TargetBoneParentMap.Reserve(RefSkeleton.GetNum());
    
    for (int32 i = 0; i < RefSkeleton.GetNum(); i++)
    {
        FName BoneName = RefSkeleton.GetBoneName(i);
        FString NormalizedName = FMoABoneNameMappings::NormalizeBoneName(BoneName.ToString());

        if (!NormalizedToAllTargets.Contains(NormalizedName))
        {
            NormalizedToAllTargets.Add(NormalizedName, TArray<FName>());
        }
        NormalizedToAllTargets[NormalizedName].Add(BoneName);

        if (!NormalizedNameMap.Contains(NormalizedName))
        {
            NormalizedNameMap.Add(NormalizedName, BoneName);
        }

        int32 ParentIndex = RefSkeleton.GetParentIndex(i);
        if (ParentIndex != INDEX_NONE)
        {
            TargetBoneParentMap.Add(BoneName, RefSkeleton.GetBoneName(ParentIndex));
        }
        else
        {
            TargetBoneParentMap.Add(BoneName, NAME_None);
        }
    }
    
    int32 MappedCount = 0;
    int32 TotalBones = (int32)EMoABoneName::Count;

    DuplicateTargetBones.Empty();

    TMap<FName, TArray<FName>> TargetBoneUsage; // TargetBone -> SourceBones that map to it
    TMap<FName, TSet<FString>> TargetBoneGroups; // TargetBone -> Groups it appears in

    TMap<FString, FName> CurrentMappings;
    CurrentMappings.Reserve(TotalBones);
    
    const TMap<FString, FMoABoneHierarchyInfo>& BoneHierarchy = FMoABoneNameMappings::GetExpectedBoneHierarchy();

    TArray<TPair<EMoABoneName, int32>> BoneSortOrder; // BoneEnum -> Depth
    BoneSortOrder.Reserve(TotalBones);
    
    for (int32 i = 0; i < TotalBones; i++)
    {
        EMoABoneName BoneEnum = (EMoABoneName)i;
        FName BoneName = FMoABoneNameUtils::EnumToFName(BoneEnum);
        if (BoneName == NAME_None || BoneName.IsNone())
        {
            BoneSortOrder.Add(TPair<EMoABoneName, int32>(BoneEnum, 0));
            continue;
        }

        FString NormalizedName = FMoABoneNameMappings::NormalizeBoneName(BoneName.ToString());
        int32 Depth = 0;
        FString CurrentBone = NormalizedName;
        const int32 MaxDepth = 20; // Safety limit to prevent infinite loops
        FString LastBone;

        while (!CurrentBone.IsEmpty() && Depth < MaxDepth)
        {
            const FMoABoneHierarchyInfo* Info = BoneHierarchy.Find(CurrentBone);
            if (Info && !Info->ExpectedParent.IsEmpty())
            {
                if (Info->ExpectedParent == NormalizedName || Info->ExpectedParent == LastBone)
                {
                    break;
                }
                Depth++;
                LastBone = CurrentBone;
                CurrentBone = Info->ExpectedParent;
            }
            else
            {
                break;
            }
        }

        BoneSortOrder.Add(TPair<EMoABoneName, int32>(BoneEnum, Depth));
    }

    BoneSortOrder.Sort([](const TPair<EMoABoneName, int32>& A, const TPair<EMoABoneName, int32>& B)
    {
        return A.Value < B.Value;
    });

    // Process bones in hierarchy order
    for (const auto& BonePair : BoneSortOrder)
    {
        EMoABoneName BoneEnum = BonePair.Key;
        FName SourceBoneName = FMoABoneNameUtils::EnumToFName(BoneEnum);
        
        if (SourceBoneName == NAME_None || SourceBoneName.IsNone())
        {
            continue;
        }
        
        // Find the row in the appropriate array
        FMoABoneMappingRow* TargetRow = nullptr;
        FString GroupName;
        
        EMoABoneCategory Category = FMoABoneNameUtils::GetBoneCategory(BoneEnum);
        switch (Category)
        {
            case EMoABoneCategory::BodyAndSpine:
                for (FMoABoneMappingRow& Row : BodySpineBones)
                {
                    if (Row.SourceBone == BoneEnum) { TargetRow = &Row; GroupName = TEXT("Body/Spine"); break; }
                }
                break;
            case EMoABoneCategory::Legs:
                for (FMoABoneMappingRow& Row : LegBones)
                {
                    if (Row.SourceBone == BoneEnum) { TargetRow = &Row; GroupName = TEXT("Leg"); break; }
                }
                break;
            case EMoABoneCategory::Arms:
                for (FMoABoneMappingRow& Row : ArmBones)
                {
                    if (Row.SourceBone == BoneEnum) { TargetRow = &Row; GroupName = TEXT("Arm"); break; }
                }
                break;
            case EMoABoneCategory::Fingers:
                for (FMoABoneMappingRow& Row : FingerBones)
                {
                    if (Row.SourceBone == BoneEnum) { TargetRow = &Row; GroupName = TEXT("Finger"); break; }
                }
                break;
        }
        
        if (!TargetRow)
        {
            continue;
        }
        
        FName FoundBone = FindBestMatchWithHierarchyValidation(
            SourceBoneName, 
            NormalizedNameMap, 
            NormalizedToAllTargets,
            TargetBoneParentMap, 
            CurrentMappings,
            BoneHierarchy);

        if (FoundBone != NAME_None && !FoundBone.IsNone())
        {
            FString FoundBoneStr = FoundBone.ToString();
            FoundBoneStr.TrimStartAndEndInline();
            
            if (!FoundBoneStr.IsEmpty())
            {
                TargetRow->TargetBone = FoundBone;
                MappedCount++;
                
                // Update current mappings for hierarchy validation of subsequent bones
                FString NormalizedSourceName = FMoABoneNameMappings::NormalizeBoneName(SourceBoneName.ToString());
                CurrentMappings.Add(NormalizedSourceName, FoundBone);
                
                if (!TargetBoneUsage.Contains(FoundBone))
                {
                    TargetBoneUsage.Add(FoundBone, TArray<FName>());
                }
                TargetBoneUsage[FoundBone].Add(SourceBoneName);
                
                if (!TargetBoneGroups.Contains(FoundBone))
                {
                    TargetBoneGroups.Add(FoundBone, TSet<FString>());
                }
                TargetBoneGroups[FoundBone].Add(GroupName);
            }
        }
    }
    
    // Find duplicates and collect affected groups
    TSet<FString> AffectedGroups;
    for (const auto& Pair : TargetBoneUsage)
    {
        if (Pair.Value.Num() > 1)
        {
            // Check if duplicates are actually mutually exclusive bones
            bool bHasTrueDuplicate = false;
            for (int32 i = 0; i < Pair.Value.Num() && !bHasTrueDuplicate; ++i)
            {
                for (int32 j = i + 1; j < Pair.Value.Num(); ++j)
                {
                    if (FMoABoneNameMappings::AreBonesMutuallyExclusive(
                        Pair.Value[i].ToString(), Pair.Value[j].ToString()))
                    {
                        bHasTrueDuplicate = true;
                        break;
                    }
                }
            }
            
            if (bHasTrueDuplicate)
            {
                DuplicateTargetBones.Add(Pair.Key, Pair.Value);
                
                if (TargetBoneGroups.Contains(Pair.Key))
                {
                    for (const FString& Group : TargetBoneGroups[Pair.Key])
                    {
                        AffectedGroups.Add(Group);
                    }
                }
                
                FString SourceBoneList;
                for (int32 i = 0; i < Pair.Value.Num(); ++i)
                {
                    if (i > 0) SourceBoneList += TEXT(", ");
                    SourceBoneList += Pair.Value[i].ToString();
                }
                UE_LOG(LogTemp, Warning, TEXT("[MoA Remap] Duplicate mapping detected! Target bone '%s' is mapped from multiple source bones: [%s]. Please verify and adjust manually."),
                    *Pair.Key.ToString(), *SourceBoneList);
            }
        }
    }

    // Update duplicate status with group info
    if (DuplicateTargetBones.Num() > 0)
    {
        // Build group list
        FString GroupList;
        int32 GroupIndex = 0;
        for (const FString& Group : AffectedGroups)
        {
            if (GroupIndex > 0) GroupList += TEXT(", ");
            GroupList += Group;
            GroupIndex++;
        }
        
        DuplicateStatusText = FString::Printf(TEXT("%d duplicate(s) [%s]"), DuplicateTargetBones.Num(), *GroupList);
    }
    else
    {
        DuplicateStatusText = TEXT("OK");
    }

    // Clear cache
    BoneNameMap.Empty();
    
    if (DuplicateTargetBones.Num() > 0)
    {
        UE_LOG(LogTemp, Warning, TEXT("[MoA Remap] Found %d duplicate target bones. Check 'Mapping Statistics' for details."), 
            DuplicateTargetBones.Num());
    }
    
    // Update missing status with group info
    int32 MissingCount = TotalBones - MappedCount;
    if (MissingCount > 0)
    {
        // Count missing bones by group
        TMap<FString, int32> MissingByGroup;
        struct FGroupArrayInfo
        {
            TArray<FMoABoneMappingRow>* Array;
            FString GroupName;
        };
        FGroupArrayInfo GroupArrays[] = {
            { &BodySpineBones, TEXT("Body") },
            { &LegBones, TEXT("Leg") },
            { &ArmBones, TEXT("Arm") },
            { &FingerBones, TEXT("Finger") }
        };
        
        for (const FGroupArrayInfo& Info : GroupArrays)
        {
            if (!Info.Array) continue;
            int32 GroupMissing = 0;
            for (const FMoABoneMappingRow& Row : *Info.Array)
            {
                if (Row.TargetBone == NAME_None || Row.TargetBone.IsNone())
                {
                    GroupMissing++;
                }
            }
            if (GroupMissing > 0)
            {
                MissingByGroup.Add(Info.GroupName, GroupMissing);
            }
        }
        
        FString GroupSummary;
        for (const auto& Pair : MissingByGroup)
        {
            if (!GroupSummary.IsEmpty()) GroupSummary += TEXT(", ");
            GroupSummary += FString::Printf(TEXT("%s:%d"), *Pair.Key, Pair.Value);
        }
        
        MissingStatusText = FString::Printf(TEXT("%d unmapped [%s]"), MissingCount, *GroupSummary);
        UE_LOG(LogTemp, Warning, TEXT("[MoA Remap] %d bones were not found in the target skeleton. You may need to map them manually."), 
            MissingCount);
    }
    else
    {
        MissingStatusText = TEXT("OK");
    }
    
#if WITH_EDITOR
    // Force update the details panel
    if (GEditor)
    {
        GEditor->NoteSelectionChange();
    }
#endif
}

FName UMoARemapAssetBase::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* Result = const_cast<UMoARemapAssetBase*>(this)->FindBestMatchWithHierarchyValidationInternal(
        SourceBoneName, NormalizedNameMap, NormalizedToAllTargets, TargetBoneParentMap, CurrentMappings, BoneHierarchy);
    return Result ? *Result : NAME_None;
}

FName* UMoARemapAssetBase::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)
{
    FString SourceStr = SourceBoneName.ToString();
    FString NormalizedSource = FMoABoneNameMappings::NormalizeBoneName(SourceStr);

    const FMoABoneHierarchyInfo* SourceHierarchyInfo = BoneHierarchy.Find(NormalizedSource);
    FName ExpectedMappedParent = NAME_None;

    if (SourceHierarchyInfo && !SourceHierarchyInfo->ExpectedParent.IsEmpty())
    {
        const FName* MappedParent = CurrentMappings.Find(SourceHierarchyInfo->ExpectedParent);
        if (MappedParent)
        {
            ExpectedMappedParent = *MappedParent;
        }
    }

    TArray<FString> ExclusionPatterns;
    TArray<FString> BonusPatterns;
    
    if (NormalizedSource == TEXT("neck1"))
    {
        ExclusionPatterns.Add(TEXT("neck"));
        BonusPatterns.Add(TEXT("head"));
        BonusPatterns.Add(TEXT("skull"));
        BonusPatterns.Add(TEXT("cranium"));
    }
    else if (NormalizedSource == TEXT("neck"))
    {
        ExclusionPatterns.Add(TEXT("head"));
        ExclusionPatterns.Add(TEXT("skull"));
        ExclusionPatterns.Add(TEXT("cranium"));
        BonusPatterns.Add(TEXT("neck"));
    }

    bool bIsThumbSource = NormalizedSource.Contains(TEXT("finger0")) || NormalizedSource.Contains(TEXT("thumb"));
    bool bIsIndexSource = NormalizedSource.Contains(TEXT("finger1")) && !NormalizedSource.Contains(TEXT("finger11")) && !NormalizedSource.Contains(TEXT("finger12"));
    bool bIsMiddleSource = NormalizedSource.Contains(TEXT("finger2")) && !NormalizedSource.Contains(TEXT("finger21")) && !NormalizedSource.Contains(TEXT("finger22"));
    bool bIsRingSource = NormalizedSource.Contains(TEXT("finger3")) && !NormalizedSource.Contains(TEXT("finger31")) && !NormalizedSource.Contains(TEXT("finger32"));
    bool bIsPinkySource = NormalizedSource.Contains(TEXT("finger4")) && !NormalizedSource.Contains(TEXT("finger41")) && !NormalizedSource.Contains(TEXT("finger42"));

    if (NormalizedSource.Contains(TEXT("finger01")) || NormalizedSource.Contains(TEXT("finger02"))) bIsThumbSource = true;
    if (NormalizedSource.Contains(TEXT("finger11")) || NormalizedSource.Contains(TEXT("finger12"))) bIsIndexSource = true;
    if (NormalizedSource.Contains(TEXT("finger21")) || NormalizedSource.Contains(TEXT("finger22"))) bIsMiddleSource = true;
    if (NormalizedSource.Contains(TEXT("finger31")) || NormalizedSource.Contains(TEXT("finger32"))) bIsRingSource = true;
    if (NormalizedSource.Contains(TEXT("finger41")) || NormalizedSource.Contains(TEXT("finger42"))) bIsPinkySource = true;
    
    if (bIsThumbSource)
    {
        ExclusionPatterns.Add(TEXT("index"));
        ExclusionPatterns.Add(TEXT("middle"));
        ExclusionPatterns.Add(TEXT("mid"));
        ExclusionPatterns.Add(TEXT("ring"));
        ExclusionPatterns.Add(TEXT("pinky"));
        ExclusionPatterns.Add(TEXT("little"));
        BonusPatterns.Add(TEXT("thumb"));
    }
    else if (bIsIndexSource)
    {
        ExclusionPatterns.Add(TEXT("thumb"));
        ExclusionPatterns.Add(TEXT("middle"));
        ExclusionPatterns.Add(TEXT("mid"));
        ExclusionPatterns.Add(TEXT("ring"));
        ExclusionPatterns.Add(TEXT("pinky"));
        ExclusionPatterns.Add(TEXT("little"));
        BonusPatterns.Add(TEXT("index"));
    }
    else if (bIsMiddleSource)
    {
        ExclusionPatterns.Add(TEXT("thumb"));
        ExclusionPatterns.Add(TEXT("index"));
        ExclusionPatterns.Add(TEXT("ring"));
        ExclusionPatterns.Add(TEXT("pinky"));
        ExclusionPatterns.Add(TEXT("little"));
        BonusPatterns.Add(TEXT("middle"));
        BonusPatterns.Add(TEXT("mid"));
    }
    else if (bIsRingSource)
    {
        ExclusionPatterns.Add(TEXT("thumb"));
        ExclusionPatterns.Add(TEXT("index"));
        ExclusionPatterns.Add(TEXT("middle"));
        ExclusionPatterns.Add(TEXT("mid"));
        ExclusionPatterns.Add(TEXT("pinky"));
        ExclusionPatterns.Add(TEXT("little"));
        BonusPatterns.Add(TEXT("ring"));
    }
    else if (bIsPinkySource)
    {
        ExclusionPatterns.Add(TEXT("thumb"));
        ExclusionPatterns.Add(TEXT("index"));
        ExclusionPatterns.Add(TEXT("middle"));
        ExclusionPatterns.Add(TEXT("mid"));
        ExclusionPatterns.Add(TEXT("ring"));
        BonusPatterns.Add(TEXT("pinky"));
        BonusPatterns.Add(TEXT("little"));
    }
    
    TArray<TPair<FName, float>> ScoredCandidates; // Candidate -> Score
    ScoredCandidates.Reserve(20);

    auto AddCandidate = [&](FName CandidateBone, float BaseScore)
    {
        if (CandidateBone == NAME_None || CandidateBone.IsNone())
        {
            return;
        }

        FString CandidateLower = CandidateBone.ToString().ToLower();
        FString CandidateNormalizedForCheck = FMoABoneNameMappings::NormalizeBoneName(CandidateLower);

        for (const FString& ExcludePattern : ExclusionPatterns)
        {
            if (CandidateNormalizedForCheck.Contains(ExcludePattern) || CandidateLower.Contains(ExcludePattern))
            {
                return;
            }
        }

        float FinalScore = BaseScore;

        for (const FString& BonusPattern : BonusPatterns)
        {
            if (CandidateNormalizedForCheck.Contains(BonusPattern) || CandidateLower.Contains(BonusPattern))
            {
                FinalScore *= 1.5f;
                break;
            }
        }
        
        if (ExpectedMappedParent != NAME_None)
        {
            const FName* CandidateParent = TargetBoneParentMap.Find(CandidateBone);
            if (CandidateParent && *CandidateParent != NAME_None)
            {
                if (*CandidateParent == ExpectedMappedParent)
                {
                    FinalScore *= 2.0f;
                }
                else
                {
                    bool bFoundInChain = false;
                    FName CurrentParent = *CandidateParent;
                    int32 ChainDepth = 0;
                    const int32 MaxChainDepth = 3;

                    while (CurrentParent != NAME_None && ChainDepth < MaxChainDepth)
                    {
                        if (CurrentParent == ExpectedMappedParent)
                        {
                            bFoundInChain = true;
                            FinalScore *= (1.5f - ChainDepth * 0.15f);
                            break;
                        }
                        const FName* NextParent = TargetBoneParentMap.Find(CurrentParent);
                        CurrentParent = NextParent ? *NextParent : NAME_None;
                        ChainDepth++;
                    }

                    if (!bFoundInChain)
                    {
                        FinalScore *= 0.3f;
                    }
                }
            }
        }
        
        bool bSourceIsLeft = NormalizedSource.Contains(TEXT("left"));
        bool bSourceIsRight = NormalizedSource.Contains(TEXT("right"));

        FString CandidateOriginal = CandidateBone.ToString().ToLower();
        FString CandidateNormalized = FMoABoneNameMappings::NormalizeBoneName(CandidateOriginal);

        auto IsValidSidePattern = [](const FString& Str, const FString& Pattern, bool bCheckAfter) -> bool
        {
            int32 Idx = Str.Find(Pattern);
            if (Idx == INDEX_NONE) return false;

            if (bCheckAfter)
            {
                int32 AfterIdx = Idx + Pattern.Len();
                if (AfterIdx >= Str.Len()) return true;
                TCHAR NextChar = Str[AfterIdx];
                return FChar::IsDigit(NextChar) || NextChar == TEXT('_') || NextChar == TEXT('.') || NextChar == TEXT('-');
            }
            return true;
        };
        
        // Check various left/right patterns in original name
        // Priority: prefix patterns (l_, r_) first, then suffix patterns
        bool bCandidateIsLeft = CandidateOriginal.Contains(TEXT("left")) ||
                               CandidateOriginal.StartsWith(TEXT("l_")) ||
                               CandidateOriginal.StartsWith(TEXT("l.")) ||
                               CandidateOriginal.StartsWith(TEXT("l-")) ||
                               CandidateOriginal.EndsWith(TEXT("_l")) ||
                               CandidateOriginal.EndsWith(TEXT(".l")) ||
                               CandidateOriginal.EndsWith(TEXT("-l")) ||
                               CandidateOriginal.EndsWith(TEXT(" l")) ||
                               IsValidSidePattern(CandidateOriginal, TEXT("_l"), true) ||
                               IsValidSidePattern(CandidateOriginal, TEXT(".l"), true) ||
                               (CandidateNormalized.EndsWith(TEXT("l")) && !CandidateNormalized.EndsWith(TEXT("ll")) && 
                                !CandidateNormalized.EndsWith(TEXT("oll")) && !CandidateNormalized.EndsWith(TEXT("all")));
        
        bool bCandidateIsRight = CandidateOriginal.Contains(TEXT("right")) ||
                                CandidateOriginal.StartsWith(TEXT("r_")) ||
                                CandidateOriginal.StartsWith(TEXT("r.")) ||
                                CandidateOriginal.StartsWith(TEXT("r-")) ||
                                CandidateOriginal.EndsWith(TEXT("_r")) ||
                                CandidateOriginal.EndsWith(TEXT(".r")) ||
                                CandidateOriginal.EndsWith(TEXT("-r")) ||
                                CandidateOriginal.EndsWith(TEXT(" r")) ||
                                IsValidSidePattern(CandidateOriginal, TEXT("_r"), true) ||
                                IsValidSidePattern(CandidateOriginal, TEXT(".r"), true) ||
                                (CandidateNormalized.EndsWith(TEXT("r")) && !CandidateNormalized.EndsWith(TEXT("rr")) &&
                                 !CandidateNormalized.EndsWith(TEXT("ar")) && !CandidateNormalized.EndsWith(TEXT("er")));
        
        // If both left and right detected (e.g., "l_ring1" has both l_ and _r in "ring"),
        // prioritize the prefix pattern
        if (bCandidateIsLeft && bCandidateIsRight)
        {
            // Check which pattern is more explicit
            bool bHasLeftPrefix = CandidateOriginal.StartsWith(TEXT("l_")) || 
                                  CandidateOriginal.StartsWith(TEXT("l.")) ||
                                  CandidateOriginal.StartsWith(TEXT("l-")) ||
                                  CandidateOriginal.Contains(TEXT("left"));
            bool bHasRightPrefix = CandidateOriginal.StartsWith(TEXT("r_")) || 
                                   CandidateOriginal.StartsWith(TEXT("r.")) ||
                                   CandidateOriginal.StartsWith(TEXT("r-")) ||
                                   CandidateOriginal.Contains(TEXT("right"));
            
            if (bHasLeftPrefix && !bHasRightPrefix)
            {
                bCandidateIsRight = false; // It's actually Left
            }
            else if (bHasRightPrefix && !bHasLeftPrefix)
            {
                bCandidateIsLeft = false; // It's actually Right
            }
        }
        
        if ((bSourceIsLeft && bCandidateIsRight) || (bSourceIsRight && bCandidateIsLeft))
        {
            FinalScore = 0.0f;
        }
        
        // Check if already mapped to another source (avoid duplicates)
        for (const auto& ExistingMapping : CurrentMappings)
        {
            if (ExistingMapping.Value == CandidateBone && ExistingMapping.Key != NormalizedSource)
            {
                FinalScore *= 0.1f;
                break;
            }
        }
        
        if (FinalScore > 0.0f)
        {
            // Check if already in list
            bool bFound = false;
            for (auto& Existing : ScoredCandidates)
            {
                if (Existing.Key == CandidateBone)
                {
                    // Keep higher score
                    Existing.Value = FMath::Max(Existing.Value, FinalScore);
                    bFound = true;
                    break;
                }
            }
            if (!bFound)
            {
                ScoredCandidates.Add(TPair<FName, float>(CandidateBone, FinalScore));
            }
        }
    };

    bool bIsNeckSource = (SourceStr == TEXT("Neck"));
    bool bIsNeck1Source = (SourceStr == TEXT("Neck1"));

    if (bIsNeckSource)
    {
        TArray<FString> NeckPreferences = {TEXT("neck_01"), TEXT("neck01"), TEXT("neck_1"), TEXT("neck"), TEXT("neck_02"), TEXT("neck1")};
        float PrefScore = 1.5f;
        for (const FString& Pref : NeckPreferences)
        {
            FString NormalizedPref = FMoABoneNameMappings::NormalizeBoneName(Pref);
            if (const FName* Found = NormalizedNameMap.Find(NormalizedPref))
            {
                AddCandidate(*Found, PrefScore);
            }
            if (const TArray<FName>* AllMatches = NormalizedToAllTargets.Find(NormalizedPref))
            {
                for (const FName& Match : *AllMatches)
                {
                    AddCandidate(Match, PrefScore * 0.9f);
                }
            }
            PrefScore -= 0.05f;
        }
    }
    else if (bIsNeck1Source)
    {
        TArray<FString> HeadPreferences = {TEXT("head"), TEXT("skull"), TEXT("cranium"), TEXT("head1"), TEXT("head01"), TEXT("head_01")};
        float PrefScore = 2.0f;
        for (const FString& Pref : HeadPreferences)
        {
            FString NormalizedPref = FMoABoneNameMappings::NormalizeBoneName(Pref);
            if (const FName* Found = NormalizedNameMap.Find(NormalizedPref))
            {
                AddCandidate(*Found, PrefScore);
            }
            if (const TArray<FName>* AllMatches = NormalizedToAllTargets.Find(NormalizedPref))
            {
                for (const FName& Match : *AllMatches)
                {
                    AddCandidate(Match, PrefScore * 0.9f);
                }
            }
            PrefScore -= 0.1f;
        }
    }
    
    // Add exact match candidate (highest priority)
    if (const FName* Found = NormalizedNameMap.Find(NormalizedSource))
    {
        AddCandidate(*Found, 1.0f);
    }
    if (const TArray<FName>* AllMatches = NormalizedToAllTargets.Find(NormalizedSource))
    {
        for (const FName& Match : *AllMatches)
        {
            AddCandidate(Match, 0.95f);
        }
    }
    
    // Add variation candidates
    TArray<FString> Variations = FMoABoneNameMappings::GenerateVariations(SourceStr);
    for (int32 i = 0; i < Variations.Num(); i++)
    {
        const FString& Variation = Variations[i];
        FString NormalizedVariation = FMoABoneNameMappings::NormalizeBoneName(Variation);
        
        // Score decreases with variation index
        float VariationScore = 0.9f - (i * 0.01f);
        VariationScore = FMath::Max(VariationScore, 0.5f);
        
        if (const FName* Found = NormalizedNameMap.Find(NormalizedVariation))
        {
            AddCandidate(*Found, VariationScore);
        }
        if (const TArray<FName>* AllMatches = NormalizedToAllTargets.Find(NormalizedVariation))
        {
            for (const FName& Match : *AllMatches)
            {
                AddCandidate(Match, VariationScore * 0.9f);
            }
        }
    }
    
    if (ScoredCandidates.Num() == 0)
    {
        UE_LOG(LogTemp, Warning, TEXT("[MoA Remap] No candidates found for source bone: %s"), *SourceStr);
        return nullptr;
    }

    ScoredCandidates.Sort([](const TPair<FName, float>& A, const TPair<FName, float>& B)
    {
        return A.Value > B.Value;
    });

    FString NormalizedBest = FMoABoneNameMappings::NormalizeBoneName(ScoredCandidates[0].Key.ToString());
    return const_cast<TMap<FString, FName>&>(NormalizedNameMap).Find(NormalizedBest);
}

void UMoARemapAssetBase::CalculateTPoseDeltasFromReferenceSkeleton()
{
    if (!TPoseReferenceSkeleton || !TargetSkeleton)
    {
        TPoseRotationDeltaMap.Empty();
        return;
    }

    // Collect all bone mappings
    TArray<FMoABoneMappingRow> AllBoneMappings = GetAllBones();

    TPoseRotationDeltaMap = FMoATposeCalculator::CalculateTPoseDeltas(
        TargetSkeleton,
        TPoseReferenceSkeleton,
        AllBoneMappings);
}

void UMoARemapAssetBase::CalculateHipHeightOffset()
{
    if (!TargetSkeleton)
    {
        CachedTargetHipToAnkle = 0.0f;
        CachedTargetAnkleToMeshBottom = 0.0f;
        CachedMeshFloorOffset = 0.0f;
        bHeightCacheValid = false;
        bMeshFloorCalibrated = false;
        return;
    }

    FString SkeletonName = TargetSkeleton->GetName();
    TArray<FMoABoneMappingRow> AllBoneMappings = GetAllBones();
    const FReferenceSkeleton& RefSkeleton = TargetSkeleton->GetReferenceSkeleton();

    // Find bone names from mappings
    FName HipsBoneName = NAME_None;
    FName LeftAnkleBoneName = NAME_None;
    FName RightAnkleBoneName = NAME_None;
    
    for (const FMoABoneMappingRow& Row : AllBoneMappings)
    {
        FName SrcName = FMoABoneNameUtils::EnumToFName(Row.SourceBone);
        if (SrcName == TEXT("Hips")) HipsBoneName = Row.TargetBone;
        else if (SrcName == TEXT("LeftAnkle")) LeftAnkleBoneName = Row.TargetBone;
        else if (SrcName == TEXT("RightAnkle")) RightAnkleBoneName = Row.TargetBone;
    }

    int32 HipsIdx = RefSkeleton.FindBoneIndex(HipsBoneName);
    if (HipsIdx == INDEX_NONE)
    {
        UE_LOG(LogTemp, Warning, TEXT("[MoA Remap][%s] Hips bone not found (mapped: %s)"), *SkeletonName, *HipsBoneName.ToString());
        bHeightCacheValid = false;
        return;
    }

    // Get Hips Z position in T-Pose
    FVector HipsPos = FMoATposeCalculator::CalculateWorldPosition(RefSkeleton, HipsIdx);
    float HipsZ = HipsPos.Z;

    // Get Ankle Z position in T-Pose (use lowest of left/right)
    float AnkleZ = TNumericLimits<float>::Max();
    int32 LeftAnkleIdx = RefSkeleton.FindBoneIndex(LeftAnkleBoneName);
    int32 RightAnkleIdx = RefSkeleton.FindBoneIndex(RightAnkleBoneName);
    
    if (LeftAnkleIdx != INDEX_NONE)
    {
        FVector LeftAnklePos = FMoATposeCalculator::CalculateWorldPosition(RefSkeleton, LeftAnkleIdx);
        AnkleZ = FMath::Min(AnkleZ, LeftAnklePos.Z);
    }
    if (RightAnkleIdx != INDEX_NONE)
    {
        FVector RightAnklePos = FMoATposeCalculator::CalculateWorldPosition(RefSkeleton, RightAnkleIdx);
        AnkleZ = FMath::Min(AnkleZ, RightAnklePos.Z);
    }
    
    if (AnkleZ == TNumericLimits<float>::Max())
    {
        UE_LOG(LogTemp, Warning, TEXT("[MoA Remap][%s] Ankle bones not found!"), *SkeletonName);
        bHeightCacheValid = false;
        return;
    }

    // Calculate Hips to Ankle distance
    CachedTargetHipToAnkle = HipsZ - AnkleZ;

    // Get mesh bottom Z from bounds (vertex access requires game thread)
    float MeshBottomZ = AnkleZ;
    
#if WITH_EDITOR
    if (IsInGameThread())  // Only access mesh data from game thread
    {
        USkeletalMesh* PreviewMesh = TargetSkeleton->GetPreviewMesh();
        if (PreviewMesh)
        {
            FSkeletalMeshRenderData* RenderData = PreviewMesh->GetResourceForRendering();
            if (RenderData && RenderData->LODRenderData.Num() > 0)
            {
                const FSkeletalMeshLODRenderData& LODData = RenderData->LODRenderData[0];
                const FPositionVertexBuffer& VertexBuffer = LODData.StaticVertexBuffers.PositionVertexBuffer;
                
                float LowestZ = TNumericLimits<float>::Max();
                const uint32 NumVertices = VertexBuffer.GetNumVertices();
                
                for (uint32 i = 0; i < NumVertices; i++)
                {
                    LowestZ = FMath::Min(LowestZ, VertexBuffer.VertexPosition(i).Z);
                }
                
                if (LowestZ != TNumericLimits<float>::Max())
                {
                    MeshBottomZ = LowestZ;
                }
            }
        }
    }
#endif

    CachedTargetAnkleToMeshBottom = AnkleZ - MeshBottomZ;
    bHeightCacheValid = true;
    bMeshFloorCalibrated = false;
}

void UMoARemapAssetBase::UpdateTPoseRotationDeltas(const FBoneContainer& BoneContainer)
{
    USkeleton* SkeletonAsset = BoneContainer.GetSkeletonAsset();
    if (!SkeletonAsset)
    {
        TPoseRotationDeltaMap.Empty();
        CachedTPoseSkeletonName = NAME_None;
        return;
    }

    const FName CurrentSkeletonName = SkeletonAsset->GetFName();

    // Check if cache is still valid
    if (CachedTPoseSkeletonName == CurrentSkeletonName && TPoseRotationDeltaMap.Num() > 0)
    {
        return; // Cache is valid, no need to recalculate
    }

    CachedTPoseSkeletonName = CurrentSkeletonName;

    // Use TPoseReferenceSkeleton with FMoATposeCalculator
    if (TPoseReferenceSkeleton)
    {
        CalculateTPoseDeltasFromReferenceSkeleton();
    }
    else
    {
        TPoseRotationDeltaMap.Empty();
    }
}

void UMoARemapAssetBase::BuildPoseFromAnimationData(float DeltaTime, const FLiveLinkSkeletonStaticData* InSkeletonData, const FLiveLinkAnimationFrameData* InFrameData, FCompactPose& OutPose)
{
    const TArray<FName>& SourceBoneNames = InSkeletonData->BoneNames;

    const FBoneContainer& BoneContainer = OutPose.GetBoneContainer();
    UpdateTPoseRotationDeltas(BoneContainer);

	TArray<FMoABoneMappingRow> AllBoneMappings = GetAllBones();

	float MoAUpperLegLength = 0.0f;
	float MoALowerLegLength = 0.0f;
	float MoAHipToUpperLegLength = 0.0f;
	FVector MoAPositionOffset = FVector::ZeroVector;
	float HeightOffset = 0.0f;
	
	if (InFrameData->PropertyValues.Num() >= 3)
	{
		MoAUpperLegLength = InFrameData->PropertyValues[0] * 100.0f; // m -> cm
		MoALowerLegLength = InFrameData->PropertyValues[1] * 100.0f;
		MoAHipToUpperLegLength = InFrameData->PropertyValues[2] * 100.0f;
	}
	
	if (InFrameData->PropertyValues.Num() >= 6)
	{
		MoAPositionOffset.X = InFrameData->PropertyValues[3] * 100.0f; // m -> cm
		MoAPositionOffset.Y = InFrameData->PropertyValues[4] * 100.0f;
		MoAPositionOffset.Z = InFrameData->PropertyValues[5] * 100.0f;
	}
	
	// HeightOffset is already in cm, no conversion needed (backward compatible - defaults to 0 if not present)
	if (InFrameData->PropertyValues.Num() >= 7)
	{
		HeightOffset = InFrameData->PropertyValues[6];
	}

	float MoATotalLegLength = MoAHipToUpperLegLength + MoAUpperLegLength + MoALowerLegLength;

    TArray<FName, TMemStackAllocator<>> TransformedBoneNames;
    TransformedBoneNames.Reserve(SourceBoneNames.Num());

    TMap<FName, FName> LocalBoneMap;
    LocalBoneMap.Reserve(AllBoneMappings.Num());
    for (const FMoABoneMappingRow& Row : AllBoneMappings)
    {
        if (Row.TargetBone != NAME_None)
        {
            LocalBoneMap.Add(FMoABoneNameUtils::EnumToFName(Row.SourceBone), Row.TargetBone);
        }
    }

    // Find remapped bone names
    for (const FName& SrcBoneName : SourceBoneNames)
    {
        if (const FName* TargetBoneName = LocalBoneMap.Find(SrcBoneName))
        {
            TransformedBoneNames.Add(*TargetBoneName);
        }
        else
        {
            TransformedBoneNames.Add(GetRemappedBoneName(SrcBoneName));
        }
    }

    TMap<FName, FQuat> WorldRefRotationsMap;
    TMap<FName, FQuat> TPoseCorrectedRefRotationsMap;

    TMap<FName, FName> SourceBoneParentMap;
    for (int32 i = 0; i < SourceBoneNames.Num() && i < InSkeletonData->BoneParents.Num(); i++)
    {
        int32 ParentIndex = InSkeletonData->BoneParents[i];
        if (ParentIndex >= 0 && ParentIndex < SourceBoneNames.Num())
        {
            SourceBoneParentMap.Add(TransformedBoneNames[i], TransformedBoneNames[ParentIndex]);
        }
    }

    TMap<FName, FQuat> UnmappedBoneSourceRotations; // Source bone name -> accumulated world rotation from source
    
    for (int32 i = 0; i < TransformedBoneNames.Num(); i++)
    {
        FName BoneName = TransformedBoneNames[i];
        FName SourceBoneNameForRotation = (i < SourceBoneNames.Num()) ? SourceBoneNames[i] : NAME_None;
        const int32 MeshIndex = OutPose.GetBoneContainer().GetPoseBoneIndexForBoneName(BoneName);
        
        if (MeshIndex != INDEX_NONE)
        {
            FCompactPoseBoneIndex CPIndex = OutPose.GetBoneContainer().MakeCompactPoseIndex(
                FMeshPoseBoneIndex(MeshIndex));
            
            if (CPIndex != INDEX_NONE)
            {
                auto RefBoneTransform = OutPose.GetRefPose(CPIndex);
                FQuat LocalRefRotation = RefBoneTransform.GetRotation();

                // Apply T-pose delta from TPoseRotationDeltaMap
                if (const FQuat* DeltaPtr = TPoseRotationDeltaMap.Find(BoneName))
                {
                    if (!DeltaPtr->IsIdentity())
                    {
                        LocalRefRotation = (*DeltaPtr) * LocalRefRotation;
                    }
                }
                
                TPoseCorrectedRefRotationsMap.Add(BoneName, LocalRefRotation);
                
                // Find parent bone name from source parent map
                FName* ParentBoneNamePtr = SourceBoneParentMap.Find(BoneName);
                if (ParentBoneNamePtr && WorldRefRotationsMap.Contains(*ParentBoneNamePtr))
                {
                    // Check for intermediate bones between MoA parent and current bone in target skeleton
                    const FReferenceSkeleton& RefSkel = OutPose.GetBoneContainer().GetReferenceSkeleton();
                    int32 CurrentBoneIdx = RefSkel.FindBoneIndex(BoneName);
                    FName MoAParentBoneName = *ParentBoneNamePtr;
                    
                    // Collect intermediate bones
                    TArray<int32> IntermediateBoneIndices;
                    if (CurrentBoneIdx != INDEX_NONE)
                    {
                        int32 ParentIdx = RefSkel.GetParentIndex(CurrentBoneIdx);
                        while (ParentIdx != INDEX_NONE)
                        {
                            FName ParentBoneName = RefSkel.GetBoneName(ParentIdx);
                            if (ParentBoneName == MoAParentBoneName)
                            {
                                break; // Found MoA parent
                            }
                            IntermediateBoneIndices.Add(ParentIdx);
                            ParentIdx = RefSkel.GetParentIndex(ParentIdx);
                        }
                    }
                    
                    // Start with MoA parent's world rotation
                    FQuat WorldRotation = WorldRefRotationsMap[MoAParentBoneName];
                    
                    // Add intermediate bones' local rotations (from parent to current)
                    for (int32 j = IntermediateBoneIndices.Num() - 1; j >= 0; --j)
                    {
                        int32 IntermediateIdx = IntermediateBoneIndices[j];
                        FTransform IntermediateTransform = RefSkel.GetRefBonePose()[IntermediateIdx];
                        FQuat IntermediateLocalRot = IntermediateTransform.GetRotation();
                        
                        // Apply T-Pose correction if available
                        FName IntermediateBoneName = RefSkel.GetBoneName(IntermediateIdx);
                        if (const FQuat* DeltaPtr = TPoseRotationDeltaMap.Find(IntermediateBoneName))
                        {
                            if (!DeltaPtr->IsIdentity())
                            {
                                IntermediateLocalRot = (*DeltaPtr) * IntermediateLocalRot;
                            }
                        }
                        
                        WorldRotation = WorldRotation * IntermediateLocalRot;
                    }
                    
                    // Add current bone's local rotation
                    WorldRotation = WorldRotation * LocalRefRotation;
                    WorldRefRotationsMap.Add(BoneName, WorldRotation);
                }
                else
                {
                    // Root bone or parent not found in LiveLink hierarchy
                    // Accumulate ALL ancestor rotations from target skeleton (Root �� ... �� Parent)
                    const FReferenceSkeleton& RefSkel = OutPose.GetBoneContainer().GetReferenceSkeleton();
                    int32 BoneIndex = RefSkel.FindBoneIndex(BoneName);
                    
                    FQuat WorldRotation = LocalRefRotation;
                    
                    if (BoneIndex != INDEX_NONE)
                    {
                        // Collect all ancestors from root to parent
                        TArray<int32> AncestorIndices;
                        int32 CurrentIdx = RefSkel.GetParentIndex(BoneIndex);
                        while (CurrentIdx != INDEX_NONE)
                        {
                            AncestorIndices.Add(CurrentIdx);
                            CurrentIdx = RefSkel.GetParentIndex(CurrentIdx);
                        }
                        
                        // Accumulate rotations from root to parent (reverse order)
                        FQuat AccumulatedParentRotation = FQuat::Identity;
                        for (int32 j = AncestorIndices.Num() - 1; j >= 0; --j)
                        {
                            int32 AncestorIdx = AncestorIndices[j];
                            FTransform AncestorRefTransform = RefSkel.GetRefBonePose()[AncestorIdx];
                            FQuat AncestorLocalRotation = AncestorRefTransform.GetRotation();
                            
                            // Apply T-Pose correction if available
                            FName AncestorBoneName = RefSkel.GetBoneName(AncestorIdx);
                            if (const FQuat* DeltaPtr = TPoseRotationDeltaMap.Find(AncestorBoneName))
                            {
                                if (!DeltaPtr->IsIdentity())
                                {
                                    AncestorLocalRotation = (*DeltaPtr) * AncestorLocalRotation;
                                }
                            }
                            
                            AccumulatedParentRotation = AccumulatedParentRotation * AncestorLocalRotation;
                        }
                        
                        // WorldRotation = AllAncestors * LocalRotation
                        WorldRotation = AccumulatedParentRotation * LocalRefRotation;
                    }
                    
                    WorldRefRotationsMap.Add(BoneName, WorldRotation);
                }
            }
        }
        else
        {
            FQuat ParentWorldRefRotation = FQuat::Identity;
            
            FName* ParentBoneNamePtr = SourceBoneParentMap.Find(BoneName);
            if (ParentBoneNamePtr && WorldRefRotationsMap.Contains(*ParentBoneNamePtr))
            {
                ParentWorldRefRotation = WorldRefRotationsMap[*ParentBoneNamePtr];
            }
            
            WorldRefRotationsMap.Add(BoneName, ParentWorldRefRotation);
            
            FQuat SourceWorldRotation = ParentWorldRefRotation;
            if (i < InFrameData->Transforms.Num())
            {
                FQuat SourceLocalRotation = ConvertBoneRotation(InFrameData->Transforms[i].GetRotation());
                SourceWorldRotation = ParentWorldRefRotation * SourceLocalRotation;
            }
            UnmappedBoneSourceRotations.Add(BoneName, SourceWorldRotation);
            TPoseCorrectedRefRotationsMap.Add(BoneName, FQuat::Identity);
        }
    }

    TMap<FName, FQuat> SpineRotationDistribution; // Additional rotation to apply to each mapped spine bone
    {
        TArray<FName> MoASpineBones = { FName("Chest"), FName("Chest1"), FName("Chest2") };
        TArray<int32> MoASpineIndices;
        TArray<FName> MappedSpineBones;
        TArray<FQuat> UnmappedSpineRotations;
        
        // Find indices and mapping status for each spine bone
        for (const FName& SpineBone : MoASpineBones)
        {
            int32 SourceIndex = SourceBoneNames.IndexOfByKey(SpineBone);
            if (SourceIndex != INDEX_NONE && SourceIndex < TransformedBoneNames.Num())
            {
                MoASpineIndices.Add(SourceIndex);
                FName TargetBone = TransformedBoneNames[SourceIndex];
                
                // Check if this bone is actually mapped (has valid mesh index)
                const int32 MeshIndex = OutPose.GetBoneContainer().GetPoseBoneIndexForBoneName(TargetBone);
                if (MeshIndex != INDEX_NONE && TargetBone != NAME_None)
                {
                    MappedSpineBones.Add(TargetBone);
                }
                else
                {
                    // Unmapped - collect its rotation for distribution
                    if (SourceIndex < InFrameData->Transforms.Num())
                    {
                        FQuat UnmappedRotation = ConvertBoneRotation(InFrameData->Transforms[SourceIndex].GetRotation());
                        UnmappedSpineRotations.Add(UnmappedRotation);
                    }
                }
            }
        }
        
        // Distribute unmapped rotations to mapped bones
        if (UnmappedSpineRotations.Num() > 0 && MappedSpineBones.Num() > 0)
        {
            // Calculate total unmapped rotation
            FQuat TotalUnmappedRotation = FQuat::Identity;
            for (const FQuat& Rot : UnmappedSpineRotations)
            {
                // Accumulate rotations (order matters for proper blending)
                TotalUnmappedRotation = TotalUnmappedRotation * Rot;
            }
            
            // Distribute evenly among mapped bones using Slerp
            float DistributionFactor = 1.0f / MappedSpineBones.Num();
            for (const FName& MappedBone : MappedSpineBones)
            {
                // Each mapped bone gets a fraction of the total unmapped rotation
                FQuat DistributedRotation = FQuat::Slerp(FQuat::Identity, TotalUnmappedRotation, DistributionFactor);
                SpineRotationDistribution.Add(MappedBone, DistributedRotation);
            }
            
        }
    }
    
    for (int32 i = 0; i < TransformedBoneNames.Num(); i++)
    {
        FName BoneName = TransformedBoneNames[i];
        FName SourceBoneName = (i < SourceBoneNames.Num()) ? SourceBoneNames[i] : NAME_None;
        
        if (i >= InFrameData->Transforms.Num())
        {
            UE_LOG(LogTemp, Warning, TEXT("[MoA Remap] Transform index %d out of bounds (array size: %d)"), i, InFrameData->Transforms.Num());
            continue;
        }

        FTransform BoneTransform = InFrameData->Transforms[i];
        const int32 MeshIndex = OutPose.GetBoneContainer().GetPoseBoneIndexForBoneName(BoneName);
        if (MeshIndex != INDEX_NONE)
        {
            FCompactPoseBoneIndex CPIndex = OutPose.GetBoneContainer().MakeCompactPoseIndex(
                FMeshPoseBoneIndex(MeshIndex));
            if (CPIndex != INDEX_NONE)
            {
                FQuat ConvertedLiveLinkRotation;

                FQuat TPoseCorrectedRefRotation = FQuat::Identity;
                FQuat* TPoseCorrectedRefRotPtr = TPoseCorrectedRefRotationsMap.Find(BoneName);
                if (TPoseCorrectedRefRotPtr)
                {
                    TPoseCorrectedRefRotation = *TPoseCorrectedRefRotPtr;
                }

                if (SourceBoneName == TEXT("Hips") || i == 0)
                {
                    const FReferenceSkeleton& RefSkel = OutPose.GetBoneContainer().GetReferenceSkeleton();
                    int32 HipBoneIndex = RefSkel.FindBoneIndex(BoneName);

                    FVector BoneTranslation = BoneTransform.GetTranslation();

                    if (HipBoneIndex != INDEX_NONE)
                    {
                        int32 ParentBoneIndex = RefSkel.GetParentIndex(HipBoneIndex);
                        if (ParentBoneIndex != INDEX_NONE)
                        {
                            FTransform ParentRefTransform = RefSkel.GetRefBonePose()[ParentBoneIndex];
                            FQuat RootRefRotation = ParentRefTransform.GetRotation();

                            FName RootBoneName = RefSkel.GetBoneName(ParentBoneIndex);
                            if (const FQuat* RootDeltaPtr = TPoseRotationDeltaMap.Find(RootBoneName))
                            {
                                if (!RootDeltaPtr->IsIdentity())
                                {
                                    RootRefRotation = (*RootDeltaPtr) * RootRefRotation;
                                }
                            }

                            BoneTranslation = RootRefRotation.RotateVector(BoneTranslation);
                        }
                    }
                    
                    FVector ConvertedPosition = ConvertRootPosition(BoneTranslation);

                    if (MoATotalLegLength > 0.01f)
                    {
                        bool bMoALegLengthChanged = !bLegLengthCacheValid ||
                            !FMath::IsNearlyEqual(CachedMoAUpperLegLength, MoAUpperLegLength, 0.01f) ||
                            !FMath::IsNearlyEqual(CachedMoALowerLegLength, MoALowerLegLength, 0.01f) ||
                            !FMath::IsNearlyEqual(CachedMoAHipToUpperLegLength, MoAHipToUpperLegLength, 0.01f);

                        FVector PositionScale = CachedPositionScale;

                        FName LeftUpperLegBone = NAME_None;
                        FName LeftLowerLegBone = NAME_None;
                        FName LeftFootBone = NAME_None;
                        FName LeftToeBone = NAME_None;

                        for (const FMoABoneMappingRow& Row : AllBoneMappings)
                        {
                            FName SrcName = FMoABoneNameUtils::EnumToFName(Row.SourceBone);

                            if (SrcName == TEXT("LeftHip")) LeftUpperLegBone = Row.TargetBone;
                            else if (SrcName == TEXT("LeftKnee")) LeftLowerLegBone = Row.TargetBone;
                            else if (SrcName == TEXT("LeftAnkle")) LeftFootBone = Row.TargetBone;
                            else if (SrcName == TEXT("LeftToe")) LeftToeBone = Row.TargetBone;
                        }
                        
                        if (bMoALegLengthChanged)
                        {
                            // Update MoA leg length cache
                            CachedMoAUpperLegLength = MoAUpperLegLength;
                            CachedMoALowerLegLength = MoALowerLegLength;
                            CachedMoAHipToUpperLegLength = MoAHipToUpperLegLength;
                            
                            // Calculate Unreal character's leg length
                            float HipToUpperLeg = 0.0f;
                            float UpperLeg = 0.0f;
                            float LowerLeg = 0.0f;
                            
                            // Calculate bone lengths from reference pose
                            int32 HipsIdx = RefSkel.FindBoneIndex(BoneName);
                            int32 UpperLegIdx = RefSkel.FindBoneIndex(LeftUpperLegBone);
                            int32 LowerLegIdx = RefSkel.FindBoneIndex(LeftLowerLegBone);
                            int32 FootIdx = RefSkel.FindBoneIndex(LeftFootBone);
                            
                            if (HipsIdx != INDEX_NONE && UpperLegIdx != INDEX_NONE)
                            {
                                HipToUpperLeg = RefSkel.GetRefBonePose()[UpperLegIdx].GetTranslation().Size();
                            }
                            if (UpperLegIdx != INDEX_NONE && LowerLegIdx != INDEX_NONE)
                            {
                                UpperLeg = RefSkel.GetRefBonePose()[LowerLegIdx].GetTranslation().Size();
                            }
                            if (LowerLegIdx != INDEX_NONE && FootIdx != INDEX_NONE)
                            {
                                LowerLeg = RefSkel.GetRefBonePose()[FootIdx].GetTranslation().Size();
                            }
                            
                            // Update Unreal leg length cache
                            CachedUnrealHipToUpperLegLength = HipToUpperLeg;
                            CachedUnrealUpperLegLength = UpperLeg;
                            CachedUnrealLowerLegLength = LowerLeg;
                            
                            float UnrealTotalLegLength = HipToUpperLeg + UpperLeg + LowerLeg;
                            float UnrealUpperLegLength = UpperLeg;
                            
                            if (UnrealTotalLegLength > 0.01f && UnrealUpperLegLength > 0.01f)
                            {
                                float TotalLegScale = UnrealTotalLegLength / MoATotalLegLength;
                                float UpperLegScale = UnrealUpperLegLength / MoAUpperLegLength;
                                
                                if (bPreserveActorDistances)
                                {
                                    // Keep XY at 1.0 to preserve real-world relative positions between actors
                                    PositionScale = FVector(1.0f, 1.0f, UpperLegScale);
                                }
                                else
                                {
                                    // Scale XY by leg ratio for proportional movement
                                    PositionScale = FVector(TotalLegScale, TotalLegScale, UpperLegScale);
                                }
                                CachedPositionScale = PositionScale;

                                UE_LOG(LogTemp, Warning, TEXT("[MoA Remap] Position Scale: %s, Target Skeleton: %s"), *PositionScale.ToString(), *TargetSkeleton->GetName());
                            }
                            
                            bLegLengthCacheValid = true;
                            
                            // Reset floor calibration when leg length changes
                            bMeshFloorCalibrated = false;
                        }
                        
                        FVector ScaledPosition = ConvertedPosition * PositionScale;
                        
                        FVector LocalUpVector = FVector::UpVector;
                        
                        if (HipBoneIndex != INDEX_NONE)
                        {
                            int32 ParentIdx = RefSkel.GetParentIndex(HipBoneIndex);
                            if (ParentIdx != INDEX_NONE && ParentIdx < RefSkel.GetRefBonePose().Num())
                            {
                                FQuat ParentWorldRot = FQuat::Identity;
                                int32 TempIdx = ParentIdx;
                                while (TempIdx != INDEX_NONE && TempIdx < RefSkel.GetRefBonePose().Num())
                                {
                                    FQuat LocalRot = RefSkel.GetRefBonePose()[TempIdx].GetRotation();
                                    
                                    FName TempBoneName = RefSkel.GetBoneName(TempIdx);
                                    if (const FQuat* DeltaPtr = TPoseRotationDeltaMap.Find(TempBoneName))
                                    {
                                        LocalRot = (*DeltaPtr) * LocalRot;
                                    }
                                    
                                    ParentWorldRot = LocalRot * ParentWorldRot;
                                    TempIdx = RefSkel.GetParentIndex(TempIdx);
                                }
                                
                                LocalUpVector = ParentWorldRot.UnrotateVector(FVector::UpVector);
                            }
                        }
                        
                        if (bUseHipCorrection)
                        {
                            if (!bHeightCacheValid)
                            {
                                CalculateHipHeightOffset();
                            }
                            
                            if (!bMeshFloorCalibrated && bHeightCacheValid)
                            {
                                float HipHeightAlongUp = FVector::DotProduct(ScaledPosition, LocalUpVector);
                                float FirstMeshBottomHeight = HipHeightAlongUp - CachedTargetHipToAnkle - CachedTargetAnkleToMeshBottom;
                                CachedMeshFloorOffset = -FirstMeshBottomHeight;
                                bMeshFloorCalibrated = true;
                            }
                            
                            ScaledPosition += LocalUpVector * (CachedMeshFloorOffset + GroundOffset + HeightOffset);
                        }
                        else
                        {
                            ScaledPosition += LocalUpVector * (GroundOffset + HeightOffset);
                        }

                        ConvertedPosition = ScaledPosition;

                    }

                    FVector ConvertedMoAOffset = FVector(
                        -MoAPositionOffset.X,
                        MoAPositionOffset.Z,
                        MoAPositionOffset.Y
                    );
                    ConvertedPosition += ConvertedMoAOffset;
                    
                    OutPose[CPIndex].SetLocation(ConvertedPosition);
                    ConvertedLiveLinkRotation = ConvertRootRotation(BoneTransform.GetRotation());
                }
                else
                {
                    ConvertedLiveLinkRotation = ConvertBoneRotation(BoneTransform.GetRotation());
                }

                FQuat ActualWorldRefRot = FQuat::Identity;
                {
                    const FReferenceSkeleton& RefSkel = OutPose.GetBoneContainer().GetReferenceSkeleton();
                    int32 CurrentBoneIdx = RefSkel.FindBoneIndex(BoneName);
                    
                    if (CurrentBoneIdx != INDEX_NONE)
                    {
                        FName* MoAParentBoneNamePtr = SourceBoneParentMap.Find(BoneName);
                        FName MoAParentBoneName = MoAParentBoneNamePtr ? *MoAParentBoneNamePtr : NAME_None;
                        
                        TArray<int32> IntermediateBoneIndices;
                        int32 ParentIdx = RefSkel.GetParentIndex(CurrentBoneIdx);
                        
                        while (ParentIdx != INDEX_NONE)
                        {
                            FName ParentBoneName = RefSkel.GetBoneName(ParentIdx);
                            
                            if (ParentBoneName == MoAParentBoneName)
                            {
                                break;
                            }
                            
                            if (WorldRefRotationsMap.Contains(ParentBoneName))
                            {
                                MoAParentBoneName = ParentBoneName;
                                break;
                            }
                            
                            IntermediateBoneIndices.Add(ParentIdx);
                            ParentIdx = RefSkel.GetParentIndex(ParentIdx);
                        }
                        
                        FQuat* ParentWorldRefPtr = WorldRefRotationsMap.Find(MoAParentBoneName);
                        if (ParentWorldRefPtr)
                        {
                            ActualWorldRefRot = *ParentWorldRefPtr;
                        }
                        
                        for (int32 j = IntermediateBoneIndices.Num() - 1; j >= 0; --j)
                        {
                            int32 IntermediateIdx = IntermediateBoneIndices[j];
                            FTransform IntermediateTransform = RefSkel.GetRefBonePose()[IntermediateIdx];
                            FQuat IntermediateLocalRot = IntermediateTransform.GetRotation();
                            
                            FName IntermediateBoneName = RefSkel.GetBoneName(IntermediateIdx);
                            if (const FQuat* DeltaPtr = TPoseRotationDeltaMap.Find(IntermediateBoneName))
                            {
                                if (!DeltaPtr->IsIdentity())
                                {
                                    IntermediateLocalRot = (*DeltaPtr) * IntermediateLocalRot;
                                }
                            }
                            
                            ActualWorldRefRot = ActualWorldRefRot * IntermediateLocalRot;
                        }
                        
                        FQuat* CurrentBoneLocalRefPtr = TPoseCorrectedRefRotationsMap.Find(BoneName);
                        if (CurrentBoneLocalRefPtr)
                        {
                            ActualWorldRefRot = ActualWorldRefRot * (*CurrentBoneLocalRefPtr);
                        }
                    }
                }
                
                if (!ActualWorldRefRot.IsIdentity())
                {
                    ConvertedLiveLinkRotation = ActualWorldRefRot.Inverse() * ConvertedLiveLinkRotation * ActualWorldRefRot;
                }

                FQuat FinalRotation = TPoseCorrectedRefRotation * ConvertedLiveLinkRotation;
                if (const FQuat* DistributedRotation = SpineRotationDistribution.Find(BoneName))
                {
                    FinalRotation = FinalRotation * (*DistributedRotation);
                }
                
                OutPose[CPIndex].SetRotation(FinalRotation);
            }
        }
    }
}
