// Copyright (c) 2026 CelestiaDominance. All rights reserved.

#include "BlueprintGraphThemeSelectorModule.h"

#include "BlueprintGraphThemeSelectorSettings.h"
#include "Engine/Texture2D.h"
#include "GraphEditorSettings.h"
#include "ImageUtils.h"
#include "ISettingsModule.h"
#include "ISettingsSection.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "Settings/EditorStyleSettings.h"
#include "Styling/SlateColor.h"
#include "UObject/UnrealType.h"

#define LOCTEXT_NAMESPACE "FBlueprintGraphThemeSelectorModule"

namespace BlueprintGraphThemeSelector
{
    const TArray<FName>& GetNodeColorProperties()
    {
        static const TArray<FName> NodeColorProperties = {
            TEXT("EventNodeTitleColor"),
            TEXT("FunctionCallNodeTitleColor"),
            TEXT("PureFunctionCallNodeTitleColor"),
            TEXT("ParentFunctionCallNodeTitleColor"),
            TEXT("FunctionTerminatorNodeTitleColor"),
            TEXT("ExecBranchNodeTitleColor"),
            TEXT("ExecSequenceNodeTitleColor"),
            TEXT("ResultNodeTitleColor"),
            TEXT("DefaultCommentNodeTitleColor"),
            TEXT("PreviewNodeTitleColor"),
            TEXT("DefaultDataNodeTitleColor"),
            TEXT("DefaultObjectNodeTitleColor"),
            TEXT("DefaultFunctionNodeTitleColor"),
            TEXT("DefaultEventNodeTitleColor")
        };

        return NodeColorProperties;
    }

    const TArray<FName>& GetWireColorProperties()
    {
        static const TArray<FName> WireColorProperties = {
            TEXT("ExecutionPinTypeColor"),
            TEXT("BooleanPinTypeColor"),
            TEXT("BytePinTypeColor"),
            TEXT("ClassPinTypeColor"),
            TEXT("ColorPinTypeColor"),
            TEXT("FloatPinTypeColor"),
            TEXT("IntPinTypeColor"),
            TEXT("Int64PinTypeColor"),
            TEXT("NamePinTypeColor"),
            TEXT("DelegatePinTypeColor"),
            TEXT("ObjectPinTypeColor"),
            TEXT("InterfacePinTypeColor"),
            TEXT("StringPinTypeColor"),
            TEXT("TextPinTypeColor"),
            TEXT("StructPinTypeColor"),
            TEXT("WildcardPinTypeColor"),
            TEXT("VectorPinTypeColor"),
            TEXT("RotatorPinTypeColor"),
            TEXT("TransformPinTypeColor"),
            TEXT("IndexPinTypeColor")
        };

        return WireColorProperties;
    }

    const TArray<FName>& GetAdditionalWireColorProperties()
    {
        static const TArray<FName> AdditionalWireColorProperties = {
            TEXT("TraceAttackColor"),
            TEXT("TraceSustainColor"),
            TEXT("TraceReleaseColor")
        };

        return AdditionalWireColorProperties;
    }

    const TArray<FName>& GetWireThicknessProperties()
    {
        static const TArray<FName> WireThicknessProperties = {
            TEXT("DefaultExecutionWireThickness"),
            TEXT("DefaultDataWireThickness")
        };

        return WireThicknessProperties;
    }

    bool IsSupportedBackgroundImagePath(const FString& AbsolutePath)
    {
        const FString Extension = FPaths::GetExtension(AbsolutePath, true).ToLower();
        return Extension == TEXT(".png")
            || Extension == TEXT(".jpg")
            || Extension == TEXT(".jpeg")
            || Extension == TEXT(".bmp")
            || Extension == TEXT(".tga");
    }

    bool TryCreateTextureBackedBackgroundBrush(const FString& AbsolutePath, const FLinearColor& Tint, const EBlueprintGraphBackgroundImageMovement MovementMode, TStrongObjectPtr<UTexture2D>& InOutTexture, FSlateBrush& OutBrush)
    {
        if (AbsolutePath.IsEmpty() || !FPaths::FileExists(AbsolutePath) || !IsSupportedBackgroundImagePath(AbsolutePath))
        {
            return false;
        }

        UTexture2D* ImportedTexture = FImageUtils::ImportFileAsTexture2D(AbsolutePath);
        if (!ImportedTexture)
        {
            return false;
        }

        InOutTexture.Reset(ImportedTexture);

        OutBrush = FSlateBrush();
        OutBrush.DrawAs = ESlateBrushDrawType::Image;
        OutBrush.Tiling = (MovementMode == EBlueprintGraphBackgroundImageMovement::FollowGraph)
            ? ESlateBrushTileType::Both
            : ESlateBrushTileType::NoTile;
        OutBrush.ImageType = ESlateBrushImageType::FullColor;
        OutBrush.SetResourceObject(ImportedTexture);
        OutBrush.SetImageSize(FVector2D(static_cast<float>(ImportedTexture->GetSizeX()), static_cast<float>(ImportedTexture->GetSizeY())));
        OutBrush.TintColor = FSlateColor(Tint);

        return true;
    }

    bool SetColorProperty(UObject* TargetObject, const FName PropertyName, const FLinearColor& NewColor)
    {
        if (!TargetObject)
        {
            return false;
        }

        if (FProperty* Property = FindFProperty<FProperty>(TargetObject->GetClass(), PropertyName))
        {
            if (FStructProperty* StructProperty = CastField<FStructProperty>(Property))
            {
                if (StructProperty->Struct == TBaseStructure<FLinearColor>::Get())
                {
                    *StructProperty->ContainerPtrToValuePtr<FLinearColor>(TargetObject) = NewColor;
                    return true;
                }

                if (StructProperty->Struct == TBaseStructure<FSlateColor>::Get())
                {
                    *StructProperty->ContainerPtrToValuePtr<FSlateColor>(TargetObject) = FSlateColor(NewColor);
                    return true;
                }

                if (StructProperty->Struct == TBaseStructure<FColor>::Get())
                {
                    *StructProperty->ContainerPtrToValuePtr<FColor>(TargetObject) = NewColor.ToFColor(false);
                    return true;
                }
            }
        }

        return false;
    }

    bool TryGetColorProperty(UObject* TargetObject, const FName PropertyName, FLinearColor& OutColor)
    {
        if (!TargetObject)
        {
            return false;
        }

        if (FProperty* Property = FindFProperty<FProperty>(TargetObject->GetClass(), PropertyName))
        {
            if (FStructProperty* StructProperty = CastField<FStructProperty>(Property))
            {
                if (StructProperty->Struct == TBaseStructure<FLinearColor>::Get())
                {
                    OutColor = *StructProperty->ContainerPtrToValuePtr<FLinearColor>(TargetObject);
                    return true;
                }

                if (StructProperty->Struct == TBaseStructure<FSlateColor>::Get())
                {
                    OutColor = StructProperty->ContainerPtrToValuePtr<FSlateColor>(TargetObject)->GetSpecifiedColor();
                    return true;
                }

                if (StructProperty->Struct == TBaseStructure<FColor>::Get())
                {
                    OutColor = StructProperty->ContainerPtrToValuePtr<FColor>(TargetObject)->ReinterpretAsLinear();
                    return true;
                }
            }
        }

        return false;
    }

    bool TryGetFloatProperty(UObject* TargetObject, const FName PropertyName, float& OutValue)
    {
        if (!TargetObject)
        {
            return false;
        }

        if (FProperty* Property = FindFProperty<FProperty>(TargetObject->GetClass(), PropertyName))
        {
            if (FFloatProperty* FloatProperty = CastField<FFloatProperty>(Property))
            {
                OutValue = *FloatProperty->ContainerPtrToValuePtr<float>(TargetObject);
                return true;
            }

            if (FDoubleProperty* DoubleProperty = CastField<FDoubleProperty>(Property))
            {
                OutValue = static_cast<float>(*DoubleProperty->ContainerPtrToValuePtr<double>(TargetObject));
                return true;
            }
        }

        return false;
    }

    bool SetFloatProperty(UObject* TargetObject, const FName PropertyName, const float NewValue)
    {
        if (!TargetObject)
        {
            return false;
        }

        if (FProperty* Property = FindFProperty<FProperty>(TargetObject->GetClass(), PropertyName))
        {
            if (FFloatProperty* FloatProperty = CastField<FFloatProperty>(Property))
            {
                *FloatProperty->ContainerPtrToValuePtr<float>(TargetObject) = NewValue;
                return true;
            }

            if (FDoubleProperty* DoubleProperty = CastField<FDoubleProperty>(Property))
            {
                *DoubleProperty->ContainerPtrToValuePtr<double>(TargetObject) = static_cast<double>(NewValue);
                return true;
            }
        }

        return false;
    }

    void ApplyColors(UObject* TargetObject, const TArray<FName>& PropertyNames, const FLinearColor& Color)
    {
        for (const FName PropertyName : PropertyNames)
        {
            SetColorProperty(TargetObject, PropertyName, Color);
        }
    }
}

FName FBlueprintGraphThemeSelectorModule::GetModuleName()
{
    static const FName ModuleName(TEXT("BlueprintGraphThemeSelector"));
    return ModuleName;
}

void FBlueprintGraphThemeSelectorModule::CaptureBaselinesIfNeeded() const
{
    if (bBaselinesCaptured)
    {
        return;
    }

    UGraphEditorSettings* GraphEditorSettings = GetMutableDefault<UGraphEditorSettings>();
    UEditorStyleSettings* EditorStyleSettings = GetMutableDefault<UEditorStyleSettings>();
    if (!GraphEditorSettings || !EditorStyleSettings)
    {
        return;
    }

    TArray<FName> GraphColorProperties;
    GraphColorProperties.Reserve(
        BlueprintGraphThemeSelector::GetNodeColorProperties().Num()
        + BlueprintGraphThemeSelector::GetWireColorProperties().Num()
        + BlueprintGraphThemeSelector::GetAdditionalWireColorProperties().Num());
    GraphColorProperties.Append(BlueprintGraphThemeSelector::GetNodeColorProperties());
    GraphColorProperties.Append(BlueprintGraphThemeSelector::GetWireColorProperties());
    GraphColorProperties.Append(BlueprintGraphThemeSelector::GetAdditionalWireColorProperties());

    for (const FName PropertyName : GraphColorProperties)
    {
        FLinearColor BaselineColor;
        if (BlueprintGraphThemeSelector::TryGetColorProperty(GraphEditorSettings, PropertyName, BaselineColor))
        {
            GraphEditorColorBaselines.Add(PropertyName, BaselineColor);
        }
    }

    for (const FName PropertyName : BlueprintGraphThemeSelector::GetWireThicknessProperties())
    {
        float BaselineValue = 0.0f;
        if (BlueprintGraphThemeSelector::TryGetFloatProperty(GraphEditorSettings, PropertyName, BaselineValue))
        {
            GraphEditorFloatBaselines.Add(PropertyName, BaselineValue);
        }
    }

    EditorStyleGraphBackgroundBaseline = EditorStyleSettings->GraphBackgroundBrush;
    bEditorStyleUseGridBaseline = EditorStyleSettings->bUseGrid;
    bBaselinesCaptured = true;
}

void FBlueprintGraphThemeSelectorModule::RestoreGraphEditorBaselines(UGraphEditorSettings* GraphEditorSettings) const
{
    if (!GraphEditorSettings)
    {
        return;
    }

    for (const TPair<FName, FLinearColor>& Pair : GraphEditorColorBaselines)
    {
        BlueprintGraphThemeSelector::SetColorProperty(GraphEditorSettings, Pair.Key, Pair.Value);
    }

    for (const TPair<FName, float>& Pair : GraphEditorFloatBaselines)
    {
        BlueprintGraphThemeSelector::SetFloatProperty(GraphEditorSettings, Pair.Key, Pair.Value);
    }
}

void FBlueprintGraphThemeSelectorModule::RestoreEditorStyleBaselines(UEditorStyleSettings* EditorStyleSettings) const
{
    if (!EditorStyleSettings)
    {
        return;
    }

    EditorStyleSettings->GraphBackgroundBrush = EditorStyleGraphBackgroundBaseline;
    EditorStyleSettings->bUseGrid = bEditorStyleUseGridBaseline;
}

void FBlueprintGraphThemeSelectorModule::StartupModule()
{
    CaptureBaselinesIfNeeded();
    RegisterSettings();
    ApplyThemeFromSettings();
}

void FBlueprintGraphThemeSelectorModule::ShutdownModule()
{
    UnregisterSettings();
}

void FBlueprintGraphThemeSelectorModule::RegisterSettings()
{
    if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>(TEXT("Settings")))
    {
        ISettingsSectionPtr SettingsSection = SettingsModule->RegisterSettings(
            TEXT("Project"),
            TEXT("Plugins"),
            TEXT("BlueprintGraphThemeSelector"),
            LOCTEXT("RuntimeSettingsName", "Blueprint Graph Theme Selector"),
            LOCTEXT("RuntimeSettingsDescription", "Pick a visual theme for Blueprint graph nodes, wires, and optional graph background image."),
            GetMutableDefault<UBlueprintGraphThemeSelectorSettings>()
        );

        if (SettingsSection.IsValid())
        {
            SettingsSection->OnModified().BindRaw(this, &FBlueprintGraphThemeSelectorModule::HandleSettingsSaved);
        }
    }
}

void FBlueprintGraphThemeSelectorModule::UnregisterSettings()
{
    if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>(TEXT("Settings")))
    {
        SettingsModule->UnregisterSettings(TEXT("Project"), TEXT("Plugins"), TEXT("BlueprintGraphThemeSelector"));
    }
}

bool FBlueprintGraphThemeSelectorModule::HandleSettingsSaved()
{
    GetMutableDefault<UBlueprintGraphThemeSelectorSettings>()->SaveConfig();
    ApplyThemeFromSettings();
    return true;
}

void FBlueprintGraphThemeSelectorModule::ApplyThemeFromSettings() const
{
    CaptureBaselinesIfNeeded();

    UGraphEditorSettings* GraphEditorSettings = GetMutableDefault<UGraphEditorSettings>();
    UEditorStyleSettings* EditorStyleSettings = GetMutableDefault<UEditorStyleSettings>();
    const UBlueprintGraphThemeSelectorSettings* ThemeSettings = GetDefault<UBlueprintGraphThemeSelectorSettings>();
    if (!bBaselinesCaptured || !GraphEditorSettings || !EditorStyleSettings || !ThemeSettings)
    {
        return;
    }

    const TArray<FName>& NodeColorProperties = BlueprintGraphThemeSelector::GetNodeColorProperties();
    const TArray<FName>& WireColorProperties = BlueprintGraphThemeSelector::GetWireColorProperties();
    const TArray<FName>& AdditionalWireColorProperties = BlueprintGraphThemeSelector::GetAdditionalWireColorProperties();

    if (ThemeSettings->ThemePreset == EBlueprintGraphVisualTheme::Default)
    {
        RestoreGraphEditorBaselines(GraphEditorSettings);
    }
    else
    {
        FLinearColor NodeColor = FLinearColor(0.10f, 0.11f, 0.13f, 1.0f);
        FLinearColor WireColor = FLinearColor(0.26f, 0.58f, 0.96f, 0.95f);
        float WireThickness = 2.0f;

        switch (ThemeSettings->ThemePreset)
        {
        case EBlueprintGraphVisualTheme::Dark:
            NodeColor = FLinearColor(0.08f, 0.09f, 0.11f, 0.95f);
            WireColor = FLinearColor(0.30f, 0.62f, 1.00f, 0.95f);
            WireThickness = 2.0f;
            break;

        case EBlueprintGraphVisualTheme::LowOpacity:
            NodeColor = FLinearColor(0.50f, 0.50f, 0.50f, ThemeSettings->LowOpacityAmount);
            WireColor = FLinearColor(0.55f, 0.70f, 0.95f, ThemeSettings->LowOpacityAmount);
            WireThickness = 1.0f;
            break;

        case EBlueprintGraphVisualTheme::Light:
            NodeColor = FLinearColor(0.84f, 0.87f, 0.92f, 1.0f);
            WireColor = FLinearColor(0.10f, 0.36f, 0.74f, 1.0f);
            WireThickness = 1.8f;
            break;

        case EBlueprintGraphVisualTheme::Neon:
            NodeColor = FLinearColor(0.10f, 0.05f, 0.18f, 0.98f);
            WireColor = FLinearColor(0.00f, 1.00f, 0.78f, 1.0f);
            WireThickness = 2.4f;
            break;

        case EBlueprintGraphVisualTheme::Custom:
            NodeColor = ThemeSettings->CustomNodeColor;
            WireColor = ThemeSettings->CustomWireColor;
            WireThickness = ThemeSettings->CustomWireThickness;
            break;

        default:
            break;
        }

        BlueprintGraphThemeSelector::ApplyColors(GraphEditorSettings, NodeColorProperties, NodeColor);
        BlueprintGraphThemeSelector::ApplyColors(GraphEditorSettings, WireColorProperties, WireColor);

        for (const FName PropertyName : AdditionalWireColorProperties)
        {
            BlueprintGraphThemeSelector::SetColorProperty(GraphEditorSettings, PropertyName, WireColor);
        }

        BlueprintGraphThemeSelector::SetFloatProperty(GraphEditorSettings, TEXT("DefaultExecutionWireThickness"), WireThickness);
        BlueprintGraphThemeSelector::SetFloatProperty(GraphEditorSettings, TEXT("DefaultDataWireThickness"), WireThickness * 0.9f);
    }

    bool bCustomBackgroundApplied = false;

    if (ThemeSettings->bUseCustomBackgroundImage)
    {
        FString AbsoluteBackgroundImagePath = ThemeSettings->BackgroundImagePath.FilePath;
        if (!AbsoluteBackgroundImagePath.IsEmpty())
        {
            AbsoluteBackgroundImagePath = FPaths::ConvertRelativePathToFull(AbsoluteBackgroundImagePath);
        }

        FSlateBrush NewBackgroundBrush;
        const FLinearColor BackgroundTint = FLinearColor(1.0f, 1.0f, 1.0f, FMath::Clamp(ThemeSettings->BackgroundImageOpacity, 0.0f, 1.0f));
        if (BlueprintGraphThemeSelector::TryCreateTextureBackedBackgroundBrush(AbsoluteBackgroundImagePath, BackgroundTint, ThemeSettings->BackgroundImageMovementMode, CustomBackgroundTexture, NewBackgroundBrush))
        {
            EditorStyleSettings->GraphBackgroundBrush = NewBackgroundBrush;
            bCustomBackgroundApplied = true;
        }
        else
        {
            CustomBackgroundTexture.Reset();
            EditorStyleSettings->GraphBackgroundBrush = EditorStyleGraphBackgroundBaseline;
        }
    }
    else
    {
        CustomBackgroundTexture.Reset();
        EditorStyleSettings->GraphBackgroundBrush = EditorStyleGraphBackgroundBaseline;
    }

    if (bCustomBackgroundApplied && ThemeSettings->bHideGridWhenUsingCustomBackground)
    {
        EditorStyleSettings->bUseGrid = false;
    }
    else
    {
        EditorStyleSettings->bUseGrid = bEditorStyleUseGridBaseline;
    }

#if WITH_EDITOR
    GraphEditorSettings->PostEditChange();
#endif

#if WITH_EDITOR
    EditorStyleSettings->PostEditChange();
#endif
}

IMPLEMENT_MODULE(FBlueprintGraphThemeSelectorModule, BlueprintGraphThemeSelector)

#undef LOCTEXT_NAMESPACE
