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

#include "MoARemapAssetBaseCustomization.h"

#if WITH_EDITOR

#include "MoABoneMapping.h"
#include "MoARemapAssetBase.h"
#include "IDetailCustomization.h"
#include "IPropertyTypeCustomization.h"
#include "DetailWidgetRow.h"
#include "DetailLayoutBuilder.h"
#include "IDetailChildrenBuilder.h"
#include "DetailCategoryBuilder.h"
#include "PropertyHandle.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Colors/SColorBlock.h"
#include "Styling/SlateColor.h"

TSharedRef<IPropertyTypeCustomization> FMoABoneMappingRowCustomization::MakeInstance()
{
    return MakeShareable(new FMoABoneMappingRowCustomization());
}

void FMoABoneMappingRowCustomization::CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils)
{
    TSharedPtr<IPropertyHandle> SourceBoneHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMoABoneMappingRow, SourceBone));
    TSharedPtr<IPropertyHandle> TargetBoneHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMoABoneMappingRow, TargetBone));
    
    if (!SourceBoneHandle.IsValid() || !TargetBoneHandle.IsValid())
    {
        return;
    }
    
    TArray<UObject*> OuterObjects;
    PropertyHandle->GetOuterObjects(OuterObjects);
    TWeakObjectPtr<UMoARemapAssetBase> WeakRemapAsset = nullptr;
    if (OuterObjects.Num() > 0)
    {
        WeakRemapAsset = Cast<UMoARemapAssetBase>(OuterObjects[0]);
    }

    auto GetMappingState = [TargetBoneHandle]() -> bool
    {
        if (!TargetBoneHandle.IsValid()) return false;
        
        FName TargetBoneName;
        if (TargetBoneHandle->GetValue(TargetBoneName) == FPropertyAccess::Success)
        {
            return (TargetBoneName != NAME_None && !TargetBoneName.IsNone());
        }
        return false;
    };

    auto IsDuplicateMapping = [TargetBoneHandle, WeakRemapAsset]() -> bool
    {
        if (!WeakRemapAsset.IsValid() || !TargetBoneHandle.IsValid()) return false;

        FName TargetBoneName;
        if (TargetBoneHandle->GetValue(TargetBoneName) == FPropertyAccess::Success)
        {
            const TMap<FName, TArray<FName>>& Duplicates = WeakRemapAsset->GetDuplicateTargetBones();
            return Duplicates.Contains(TargetBoneName);
        }
        return false;
    };

    auto GetDuplicateTooltip = [TargetBoneHandle, WeakRemapAsset]() -> FText
    {
        if (!WeakRemapAsset.IsValid() || !TargetBoneHandle.IsValid()) return FText::GetEmpty();

        FName TargetBoneName;
        if (TargetBoneHandle->GetValue(TargetBoneName) == FPropertyAccess::Success)
        {
            const TMap<FName, TArray<FName>>& Duplicates = WeakRemapAsset->GetDuplicateTargetBones();
            if (const TArray<FName>* SourceBones = Duplicates.Find(TargetBoneName))
            {
                FString SourceList;
                for (int32 i = 0; i < SourceBones->Num(); ++i)
                {
                    if (i > 0) SourceList += TEXT(", ");
                    SourceList += (*SourceBones)[i].ToString();
                }
                return FText::FromString(FString::Printf(TEXT("Duplicate! This target bone is mapped from: %s"), *SourceList));
            }
        }
        return FText::GetEmpty();
    };

    FText SourceBoneText = NSLOCTEXT("MoABoneMapping", "Unknown", "Unknown");

    if (SourceBoneHandle.IsValid())
    {
        uint8 EnumValue;
        if (SourceBoneHandle->GetValue(EnumValue) == FPropertyAccess::Success)
        {
            const UEnum* EnumPtr = StaticEnum<EMoABoneName>();
            if (EnumPtr)
            {
                SourceBoneText = EnumPtr->GetDisplayNameTextByValue(EnumValue);
            }
        }
    }

    bool bIsMapped = GetMappingState();
    bool bIsDuplicate = IsDuplicateMapping();
    
    HeaderRow
        .NameContent()
        [
            SNew(SHorizontalBox)
            + SHorizontalBox::Slot()
            .AutoWidth()
            .VAlign(VAlign_Center)
            [
                SNew(STextBlock)
                .Text(SourceBoneText)
                .Font(IDetailLayoutBuilder::GetDetailFontBold())
                .ColorAndOpacity_Lambda([GetMappingState, IsDuplicateMapping]()
                {
                    if (IsDuplicateMapping())
                    {
                        return FSlateColor(FLinearColor(1.0f, 0.8f, 0.0f));
                    }
                    return GetMappingState() ? FSlateColor::UseForeground() : FSlateColor(FLinearColor(1.0f, 0.3f, 0.3f));
                })
            ]
            + SHorizontalBox::Slot()
            .AutoWidth()
            .Padding(8.0f, 0.0f, 0.0f, 0.0f)
            .VAlign(VAlign_Center)
            [
                SNew(STextBlock)
                .Text(NSLOCTEXT("MoABoneMapping", "Unmapped", "[!]"))
                .Font(FCoreStyle::GetDefaultFontStyle("Bold", 9))
                .ColorAndOpacity(FLinearColor(1.0f, 0.5f, 0.0f))
                .Visibility_Lambda([GetMappingState]()
                {
                    return GetMappingState() ? EVisibility::Collapsed : EVisibility::Visible;
                })
                .ToolTipText(NSLOCTEXT("MoABoneMapping", "UnmappedTooltip", "This bone is not mapped to any target bone"))
            ]
            + SHorizontalBox::Slot()
            .AutoWidth()
            .Padding(8.0f, 0.0f, 0.0f, 0.0f)
            .VAlign(VAlign_Center)
            [
                SNew(STextBlock)
                .Text(NSLOCTEXT("MoABoneMapping", "Duplicate", "[DUPLICATE]"))
                .Font(FCoreStyle::GetDefaultFontStyle("Bold", 10))
                .ColorAndOpacity(FLinearColor(1.0f, 0.8f, 0.0f))
                .Visibility_Lambda([IsDuplicateMapping]()
                {
                    return IsDuplicateMapping() ? EVisibility::Visible : EVisibility::Collapsed;
                })
                .ToolTipText_Lambda([GetDuplicateTooltip]()
                {
                    return GetDuplicateTooltip();
                })
            ]
        ]
        .ValueContent()
        .MinDesiredWidth(200.0f)
        [
            TargetBoneHandle->CreatePropertyValueWidget()
        ];
}

void FMoABoneMappingRowCustomization::CustomizeChildren(TSharedRef<IPropertyHandle> PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils)
{
}

// ============================================================================
// FMoARemapAssetBaseCustomization Implementation
// ============================================================================

TSharedRef<IDetailCustomization> FMoARemapAssetBaseCustomization::MakeInstance()
{
    return MakeShareable(new FMoARemapAssetBaseCustomization());
}

void FMoARemapAssetBaseCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
    TArray<TWeakObjectPtr<UObject>> EditingObjects;
    DetailBuilder.GetObjectsBeingCustomized(EditingObjects);
    TWeakObjectPtr<UMoARemapAssetBase> WeakRemapAsset = nullptr;
    if (EditingObjects.Num() > 0 && EditingObjects[0].IsValid())
    {
        WeakRemapAsset = Cast<UMoARemapAssetBase>(EditingObjects[0].Get());
    }

    DetailBuilder.EditCategory(TEXT("Live Link"), FText::GetEmpty(), ECategoryPriority::Important);
    IDetailCategoryBuilder& StatusCategory = DetailBuilder.EditCategory(TEXT("Bone Mapping Status"), FText::GetEmpty(), ECategoryPriority::Important);
    DetailBuilder.EditCategory(TEXT("Bone Mapping"), FText::GetEmpty(), ECategoryPriority::Default);

    TSharedPtr<IPropertyHandle> DuplicateStatusHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, DuplicateStatusText));
    if (DuplicateStatusHandle.IsValid())
    {
        DuplicateStatusHandle->MarkHiddenByCustomization();
    }
    TSharedPtr<IPropertyHandle> MissingStatusHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, MissingStatusText));
    if (MissingStatusHandle.IsValid())
    {
        MissingStatusHandle->MarkHiddenByCustomization();
    }

    if (WeakRemapAsset.IsValid())
    {
        StatusCategory.AddCustomRow(FText::FromString(TEXT("Duplicate Check")))
            .NameContent()
            [
                SNew(STextBlock)
                .Text(FText::FromString(TEXT("Duplicate Check")))
                .Font(IDetailLayoutBuilder::GetDetailFont())
            ]
            .ValueContent()
            [
                SNew(STextBlock)
                .Text_Lambda([WeakRemapAsset]() -> FText
                {
                    if (WeakRemapAsset.IsValid())
                    {
                        return FText::FromString(WeakRemapAsset->DuplicateStatusText);
                    }
                    return FText::FromString(TEXT("Invalid Asset"));
                })
                .ColorAndOpacity_Lambda([WeakRemapAsset]() -> FSlateColor
                {
                    if (WeakRemapAsset.IsValid())
                    {
                        if (WeakRemapAsset->DuplicateStatusText == TEXT("OK"))
                        {
                            return FSlateColor(FLinearColor(0.2f, 1.0f, 0.2f));
                        }
                        else
                        {
                            return FSlateColor(FLinearColor(1.0f, 0.2f, 0.2f));
                        }
                    }
                    return FSlateColor::UseForeground();
                })
                .Font(IDetailLayoutBuilder::GetDetailFontBold())
            ];

        StatusCategory.AddCustomRow(FText::FromString(TEXT("Missing Check")))
            .NameContent()
            [
                SNew(STextBlock)
                .Text(FText::FromString(TEXT("Missing Check")))
                .Font(IDetailLayoutBuilder::GetDetailFont())
            ]
            .ValueContent()
            [
                SNew(STextBlock)
                .Text_Lambda([WeakRemapAsset]() -> FText
                {
                    if (WeakRemapAsset.IsValid())
                    {
                        return FText::FromString(WeakRemapAsset->MissingStatusText);
                    }
                    return FText::FromString(TEXT("Invalid Asset"));
                })
                .ColorAndOpacity_Lambda([WeakRemapAsset]() -> FSlateColor
                {
                    if (WeakRemapAsset.IsValid())
                    {
                        if (WeakRemapAsset->MissingStatusText == TEXT("OK"))
                        {
                            return FSlateColor(FLinearColor(0.2f, 1.0f, 0.2f));
                        }
                        else
                        {
                            return FSlateColor(FLinearColor(1.0f, 0.6f, 0.2f));
                        }
                    }
                    return FSlateColor::UseForeground();
                })
                .Font(IDetailLayoutBuilder::GetDetailFontBold())
            ];
    }
    
    IDetailCategoryBuilder& BoneMappingCategory = DetailBuilder.EditCategory(TEXT("Bone Mapping"));

    TArray<TSharedPtr<IPropertyHandle>> BoneArrayHandles;
    BoneArrayHandles.Add(DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, BodySpineBones)));
    BoneArrayHandles.Add(DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, LegBones)));
    BoneArrayHandles.Add(DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, ArmBones)));
    BoneArrayHandles.Add(DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UMoARemapAssetBase, FingerBones)));

    auto GetStatsText = [BoneArrayHandles]() -> FText
    {
        int32 MappedCount = 0;
        int32 TotalCount = 0;

        for (const TSharedPtr<IPropertyHandle>& ArrayProperty : BoneArrayHandles)
        {
            if (ArrayProperty.IsValid() && ArrayProperty->IsValidHandle())
            {
                uint32 NumElements = 0;
                if (ArrayProperty->GetNumChildren(NumElements) == FPropertyAccess::Success)
                {
                    for (uint32 i = 0; i < NumElements; ++i)
                    {
                        TSharedPtr<IPropertyHandle> ElementHandle = ArrayProperty->GetChildHandle(i);
                        if (ElementHandle.IsValid())
                        {
                            TSharedPtr<IPropertyHandle> TargetBoneHandle = ElementHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMoABoneMappingRow, TargetBone));
                            if (TargetBoneHandle.IsValid())
                            {
                                FName TargetBoneName;
                                if (TargetBoneHandle->GetValue(TargetBoneName) == FPropertyAccess::Success)
                                {
                                    TotalCount++;
                                    if (TargetBoneName != NAME_None && !TargetBoneName.IsNone())
                                    {
                                        MappedCount++;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        return FText::Format(
            NSLOCTEXT("MoABoneMapping", "MappingStats", "Mapped: {0} / {1} ({2}%)"),
            FText::AsNumber(MappedCount),
            FText::AsNumber(TotalCount),
            FText::AsNumber(TotalCount > 0 ? (MappedCount * 100 / TotalCount) : 0)
        );
    };

    auto GetStatsColor = [BoneArrayHandles]() -> FSlateColor
    {
        int32 MappedCount = 0;
        int32 TotalCount = 0;

        for (const TSharedPtr<IPropertyHandle>& ArrayProperty : BoneArrayHandles)
        {
            if (ArrayProperty.IsValid() && ArrayProperty->IsValidHandle())
            {
                uint32 NumElements = 0;
                if (ArrayProperty->GetNumChildren(NumElements) == FPropertyAccess::Success)
                {
                    for (uint32 i = 0; i < NumElements; ++i)
                    {
                        TSharedPtr<IPropertyHandle> ElementHandle = ArrayProperty->GetChildHandle(i);
                        if (ElementHandle.IsValid())
                        {
                            TSharedPtr<IPropertyHandle> TargetBoneHandle = ElementHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMoABoneMappingRow, TargetBone));
                            if (TargetBoneHandle.IsValid())
                            {
                                FName TargetBoneName;
                                if (TargetBoneHandle->GetValue(TargetBoneName) == FPropertyAccess::Success)
                                {
                                    TotalCount++;
                                    if (TargetBoneName != NAME_None && !TargetBoneName.IsNone())
                                    {
                                        MappedCount++;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        if (TotalCount == 0)
        {
            return FSlateColor(FLinearColor(0.5f, 0.5f, 0.5f));
        }

        if (MappedCount == TotalCount)
        {
            return FSlateColor(FLinearColor(0.3f, 1.0f, 0.3f));
        }
        else if (MappedCount == 0)
        {
            return FSlateColor(FLinearColor(1.0f, 0.3f, 0.3f));
        }
        else
        {
            return FSlateColor(FLinearColor(1.0f, 0.8f, 0.2f));
        }
    };
    
    auto GetProgressPercent = [BoneArrayHandles]() -> float
    {
        int32 MappedCount = 0;
        int32 TotalCount = 0;

        for (const TSharedPtr<IPropertyHandle>& ArrayProperty : BoneArrayHandles)
        {
            if (ArrayProperty.IsValid() && ArrayProperty->IsValidHandle())
            {
                uint32 NumElements = 0;
                if (ArrayProperty->GetNumChildren(NumElements) == FPropertyAccess::Success)
                {
                    for (uint32 i = 0; i < NumElements; ++i)
                    {
                        TSharedPtr<IPropertyHandle> ElementHandle = ArrayProperty->GetChildHandle(i);
                        if (ElementHandle.IsValid())
                        {
                            TSharedPtr<IPropertyHandle> TargetBoneHandle = ElementHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMoABoneMappingRow, TargetBone));
                            if (TargetBoneHandle.IsValid())
                            {
                                FName TargetBoneName;
                                if (TargetBoneHandle->GetValue(TargetBoneName) == FPropertyAccess::Success)
                                {
                                    TotalCount++;
                                    if (TargetBoneName != NAME_None && !TargetBoneName.IsNone())
                                    {
                                        MappedCount++;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        return (TotalCount > 0) ? (float)MappedCount / (float)TotalCount : 0.0f;
    };

    auto GetProgressBackgroundColor = [BoneArrayHandles]() -> FLinearColor
    {
        int32 MappedCount = 0;
        int32 TotalCount = 0;

        for (const TSharedPtr<IPropertyHandle>& ArrayProperty : BoneArrayHandles)
        {
            if (ArrayProperty.IsValid() && ArrayProperty->IsValidHandle())
            {
                uint32 NumElements = 0;
                if (ArrayProperty->GetNumChildren(NumElements) == FPropertyAccess::Success)
                {
                    for (uint32 i = 0; i < NumElements; ++i)
                    {
                        TSharedPtr<IPropertyHandle> ElementHandle = ArrayProperty->GetChildHandle(i);
                        if (ElementHandle.IsValid())
                        {
                            TSharedPtr<IPropertyHandle> TargetBoneHandle = ElementHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMoABoneMappingRow, TargetBone));
                            if (TargetBoneHandle.IsValid())
                            {
                                FName TargetBoneName;
                                if (TargetBoneHandle->GetValue(TargetBoneName) == FPropertyAccess::Success)
                                {
                                    TotalCount++;
                                    if (TargetBoneName != NAME_None && !TargetBoneName.IsNone())
                                    {
                                        MappedCount++;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        if (TotalCount == 0)
        {
            return FLinearColor(0.2f, 0.2f, 0.2f, 0.3f);
        }

        if (MappedCount == TotalCount)
        {
            return FLinearColor(0.2f, 0.8f, 0.2f, 0.3f);
        }
        else if (MappedCount == 0)
        {
            return FLinearColor(0.8f, 0.2f, 0.2f, 0.3f);
        }
        else
        {
            return FLinearColor(0.9f, 0.7f, 0.1f, 0.3f);
        }
    };
    
    BoneMappingCategory.AddCustomRow(NSLOCTEXT("MoABoneMapping", "Stats", "Statistics"))
        .WholeRowContent()
        .HAlign(HAlign_Fill)
        [
            SNew(SBox)
            .Padding(FMargin(0.0f, 8.0f, 0.0f, 8.0f))
            .MinDesiredHeight(32.0f)
            [
                SNew(SOverlay)
                + SOverlay::Slot()
                .HAlign(HAlign_Fill)
                .VAlign(VAlign_Fill)
                [
                    SNew(SColorBlock)
                    .Color(FLinearColor(0.03f, 0.03f, 0.03f, 1.0f))
                ]
                + SOverlay::Slot()
                .HAlign(HAlign_Fill)
                .VAlign(VAlign_Fill)
                [
                    SNew(SHorizontalBox)
                    + SHorizontalBox::Slot()
                    .FillWidth(TAttribute<float>::CreateLambda(GetProgressPercent))
                    [
                        SNew(SColorBlock)
                        .Color_Lambda(GetProgressBackgroundColor)
                    ]
                    + SHorizontalBox::Slot()
                    .FillWidth(TAttribute<float>::CreateLambda([GetProgressPercent]()
                    {
                        return 1.0f - GetProgressPercent();
                    }))
                    [
                        SNullWidget::NullWidget
                    ]
                ]
                + SOverlay::Slot()
                .HAlign(HAlign_Center)
                .VAlign(VAlign_Center)
                .Padding(8.0f, 6.0f)
                [
                    SNew(STextBlock)
                    .Text_Lambda(GetStatsText)
                    .Font(FCoreStyle::GetDefaultFontStyle("Bold", 10))
                    .ColorAndOpacity(FLinearColor::White)
                    .Justification(ETextJustify::Center)
                    .ShadowOffset(FVector2D(1.0f, 1.0f))
                    .ShadowColorAndOpacity(FLinearColor(0.0f, 0.0f, 0.0f, 0.9f))
                ]
            ]
        ];
}

#endif // WITH_EDITOR
