#include "WingUtils.h"
#include "WingProperty.h"
#include "WingTypes.h"
#include "WingServer.h"
#include "WingTokenizer.h"
#include "PropertyHandle.h"
#include "Engine/World.h"
#include "Engine/SCS_Node.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/Kismet2NameValidators.h"
#include "UObject/UObjectIterator.h"
#include "UObject/UnrealType.h"
#include "WidgetBlueprint.h"
#include "Blueprint/WidgetTree.h"

// Blueprint override helpers
#include "EdGraphSchema_K2.h"
#include "ObjectEditorUtils.h"

// Reparent validation
#include "Engine/LevelScriptActor.h"
#include "Animation/AnimInstance.h"
#include "Blueprint/UserWidget.h"

// Animation Blueprint support
#include "AnimStateNode.h"
#include "AnimStateTransitionNode.h"
#include "AnimationStateMachineGraph.h"

// Material support
#include "Materials/Material.h"
#include "Materials/MaterialFunction.h"
#include "Materials/MaterialInstanceConstant.h"
#include "MaterialGraph/MaterialGraph.h"
#include "MaterialGraph/MaterialGraphSchema.h"
#include "IMaterialEditor.h"
#include "Subsystems/AssetEditorSubsystem.h"

// Mesh, animation, texture support
#include "Engine/StaticMesh.h"
#include "Engine/SkeletalMesh.h"
#include "Animation/AnimSequence.h"
#include "Animation/BlendSpace.h"
#include "Engine/Texture.h"


// ============================================================
// Name sanitization
// ============================================================

FString WingUtils::ExternalizeID(FName Name)
{
	return WingTokenizer::ExternalizeID(Name);
}

FName WingUtils::CheckInternalizeID(const FString &ExternalID, WingOut Errors)
{
	FString Error;
	FName InternalID = WingTokenizer::TryInternalizeID(ExternalID, Error);
	if (!Error.IsEmpty())
	{
		Errors.Printf(TEXT("%s\n"), *Error);
		UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, EscapeSequencesInFNames));
	}
	return InternalID;
}

FName WingUtils::CheckProposedName(const FString &ExternalID, const TSet<FName> &InUse, WingOut Errors)
{
	FName InternalID = CheckInternalizeID(ExternalID, Errors);
	if (InternalID.ToString().Len() > 100)
	{
		Errors.Printf(TEXT("ERROR: id %s is too long"), *ExternalID);
		return FName();
	}
	if (!InternalID.IsNone() && !WingTokenizer::WouldExternalizeReadably(InternalID))
	{
		Errors.Printf(TEXT("ERROR: id %s would not be a readable id, may not create item with this name"),
			*ExternalID);
		UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, EscapeSequencesInFNames));
		return FName();
	}
	if (InUse.Contains(InternalID))
	{
		Errors.Printf(TEXT("ERROR: id %s is already in use"), *ExternalID);
		return FName();
	}
	return InternalID;
}

FString WingUtils::StandardizeMenuItem(const FString &Item)
{
	FString Sanitized = Item;
	int32 Dst = 0;
	bool Upper = true;
	for (int32 Src = 0; Src < Sanitized.Len(); Src++)
	{
		TCHAR c = Sanitized[Src];
		if (FChar::IsAlnum(c))
		{
			if (Upper) c = FChar::ToUpper(c);
			Sanitized[Dst++] = c;
			Upper = false;
		}
		else
		{
			Upper = true;
			if ((c <= 0x20)||(c == 0x7F)) continue;
			if (c == ':') c = L'⁖';
			Sanitized[Dst++] = c;
		}
	}
	Sanitized.LeftInline(Dst);
	return Sanitized;
}


// ============================================================
// Name Lookup
// ============================================================

FString WingUtils::FormatName(const UWorld *World)
{
	return World->GetPathName();
}

FString WingUtils::FormatName(const UBlueprint *BP)
{
	return UWingTypes::TypeToTextOrDie(BP);
}

FString WingUtils::FormatName(const UActorComponent *C)
{
	return ExternalizeID(C->GetFName());
}

FString WingUtils::FormatName(const USCS_Node *Node)
{
	return ExternalizeID(Node->GetVariableName());
}

FString WingUtils::FormatName(const UEdGraph *Graph)
{
	return ExternalizeID(Graph->GetFName());
}

FString WingUtils::FormatName(const TObjectPtr<UEdGraph> &Graph)
{
	return ExternalizeID(Graph->GetFName());
}

FString WingUtils::FormatName(const UEdGraphNode* Node)
{
	return ExternalizeID(Node->GetFName());
}

FString WingUtils::FormatName(const UEdGraphPin *Pin)
{
	return ExternalizeID(Pin->GetFName());
}

FString WingUtils::FormatName(const FMemberReference &Ref)
{
	return ExternalizeID(Ref.GetMemberName());
}

FString WingUtils::FormatName(const FBPVariableDescription &Var)
{
	return ExternalizeID(Var.VarName);
}

FString WingUtils::FormatName(const UStruct *Struct)
{
	if (Cast<UScriptStruct>(Struct) || Cast<UClass>(Struct))
		return UWingTypes::TypeToTextOrDie(Struct);
	return ExternalizeID(Struct->GetFName());
}

FString WingUtils::FormatName(const UClass *Class)
{
	return UWingTypes::TypeToTextOrDie(Class);
}

FString WingUtils::FormatName(const UMaterial *Material)
{
	return Material->GetPathName();
}

FString WingUtils::FormatName(const UMaterialInstance *MaterialInstance)
{
	return MaterialInstance->GetPathName();
}

FString WingUtils::FormatName(const UMaterialFunction *MaterialFunction)
{
	return MaterialFunction->GetPathName();
}

FString WingUtils::FormatName(const UMaterialExpression *Expression)
{
	return ExternalizeID(Expression->GetFName());
}

FString WingUtils::FormatName(const UStaticMesh *Mesh)
{
	return Mesh->GetPathName();
}

FString WingUtils::FormatName(const USkeletalMesh *Mesh)
{
	return Mesh->GetPathName();
}

FString WingUtils::FormatName(const UAnimSequence *Anim)
{
	return Anim->GetPathName();
}

FString WingUtils::FormatName(const UBlendSpace *BlendSpace)
{
	return BlendSpace->GetPathName();
}

FString WingUtils::FormatName(const UTexture *Texture)
{
	return Texture->GetPathName();
}

FString WingUtils::FormatName(const UScriptStruct *Struct)
{
	return UWingTypes::TypeToTextOrDie(Struct);
}

FString WingUtils::FormatName(const UEnum *Enum)
{
	return UWingTypes::TypeToTextOrDie(Enum);
}

FString WingUtils::FormatName(const FProperty *Prop)
{
	return ExternalizeID(Prop->GetFName());
}

FString WingUtils::FormatName(IPropertyHandle &Handle)
{
	return ExternalizeID(Handle.GetProperty()->GetFName());
}

FString WingUtils::FormatName(const TSharedPtr<IPropertyHandle> &Handle)
{
	return FormatName(*Handle);
}

FString WingUtils::FormatName(const FUserPinInfo &Pin)
{
	return ExternalizeID(Pin.PinName);
}

FString WingUtils::FormatName(const FBPInterfaceDescription &IFace)
{
	return FormatName(IFace.Interface);
}

FString WingUtils::FormatName(const UWingComponentRef *Ref)
{
	return ExternalizeID(Ref->VariableName);
}

FString WingUtils::FormatName(const UWidget *Widget)
{
	return ExternalizeID(Widget->GetFName());
}

FName WingUtils::GetFName(const FWingProperty &Prop)
{
	return Prop.Prop->GetFName();
}

// ============================================================
// Formatting other things
// ============================================================

FString WingUtils::FormatNodeTitle(const UEdGraphNode *Node)
{
	FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
	int32 NewlineIdx;
	if (Title.FindChar(TEXT('\n'), NewlineIdx))
		Title.LeftInline(NewlineIdx);
	return Title;
}

// ============================================================
// JSON helpers
// ============================================================

// ============================================================
// Text formatting
// ============================================================

FString WingUtils::WrapText(const FString& Text, int32 ColLimit, const FString& Prefix)
{
	TArray<FString> Words;
	Text.ParseIntoArrayWS(Words);

	TStringBuilder<1024> Result;
	int32 Col = 0;
	for (const FString& Word : Words)
	{
		if ((Col > 0) && (Col + 1 + Word.Len() > ColLimit))
		{
			Result.Append(TEXT("\n"));
			Col = 0;
		}
		if (Col == 0)
		{
			Result.Append(Prefix);
			Result.Append(Word);
			Col = Prefix.Len() + Word.Len();
		}
		else
		{ 
			Result.Append(TEXT(" "));
			Result.Append(Word);
			Col = 1 + Word.Len();
		}
	}
	if (Col > 0) Result.Append(TEXT("\n"));
	return Result.ToString();
}

// ============================================================
// Enum helpers
// ============================================================

FString WingUtils::EnumToString(UEnum* Enum, int64 Value)
{
	return Enum->GetNameStringByValue(Value);
}

bool WingUtils::StringToEnum(UEnum* Enum, const FString& Str, int64& OutValue, WingOut Errors)
{
	int32 Index = Enum->GetIndexByNameString(Str);
	if (Index == INDEX_NONE)
	{
		FString Prefix = Enum->GenerateEnumPrefix();
		if (!Prefix.IsEmpty())
			Index = Enum->GetIndexByNameString(Prefix + TEXT("_") + Str);
	}
	if (Index == INDEX_NONE)
	{
		Errors.Printf(TEXT("ERROR: '%s' is not a valid value for %s\n"), *Str, *Enum->GetName());
		OutValue = 0;
		return false;
	}
	OutValue = Enum->GetValueByIndex(Index);
	return true;
}

// ============================================================
// Common Error Reporting
// ============================================================

bool WingUtils::CheckExactlyOneNamed(int Count, const TCHAR *Kind, const FString &Name, WingOut Errors)
{
	if (Count == 0)
	{
		Errors.Printf(TEXT("Could not find a %s named %s.\n"), Kind, *Name);
		return false;
	}
	if (Count > 1)
	{
		Errors.Printf(TEXT("More than one %s named %s\n"), Kind, *Name);
		return false;
	}
	return true;
}

bool WingUtils::CheckExactlyOneNamed(int Count, const TCHAR *Kind, FName Name, WingOut Errors)
{
	return CheckExactlyOneNamed(Count, Kind, ExternalizeID(Name), Errors);
}

bool WingUtils::CheckCanRename(UEdGraphNode* Node, const FString &Name, WingOut Errors)
{
	if (!Node->bCanRenameNode)
	{
		Errors.Printf(TEXT("ERROR: Node %s does not support renaming.\n"), *FormatName(Node));
		return false;
	}
	TSharedPtr<INameValidatorInterface> Validator = FNameValidatorFactory::MakeValidator(Node);
	EValidatorResult Result = Validator->IsValid(Name, false);
	if (Result != EValidatorResult::Ok && Result != EValidatorResult::ExistingName)
	{
		Errors.Printf(TEXT("ERROR: %s\n"), *INameValidatorInterface::GetErrorString(Name, Result));
		return false;
	}
	return true;
}

bool WingUtils::CheckNewObjectNotNull(UObject *Obj, const TCHAR *Kind, WingOut Errors)
{
	if (Obj == nullptr)
	{
		Errors.Printf(TEXT("NewObject failed to create a %s"), Kind);
		return false;
	}
	return true;
}

// ============================================================
// Blueprint override helpers
// ============================================================

bool WingUtils::CanBlueprintOverride(UBlueprint* BP, const UFunction* Function)
{
	if (!Function) return false;
	if (!UEdGraphSchema_K2::CanKismetOverrideFunction(Function)) return false;
	UClass* HiddenFromClass = BP->SkeletonGeneratedClass ? BP->SkeletonGeneratedClass->GetSuperClass() : BP->ParentClass.Get();
	if (HiddenFromClass && FObjectEditorUtils::IsFunctionHiddenFromClass(Function, HiddenFromClass)) return false;
	if (Function->HasAnyFunctionFlags(FUNC_Private)) return false;
	if (!BP->AllowFunctionOverride(Function)) return false;
	return true;
}

TArray<UFunction*> WingUtils::GetOverridableFunctions(UBlueprint *BP)
{
	TArray<UFunction *> Candidates, Results;
	TSet<UFunction*> Seen;

	UClass* ParentClass = BP->SkeletonGeneratedClass ? BP->SkeletonGeneratedClass->GetSuperClass() : BP->ParentClass.Get();
	for (TFieldIterator<UFunction> FunctionIt(ParentClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
	{
		Candidates.Add(*FunctionIt);
	}
	for (const FBPInterfaceDescription& InterfaceDesc : BP->ImplementedInterfaces)
	{
		UClass* InterfaceClass = InterfaceDesc.Interface.Get();
		if (!InterfaceClass) continue;
		for (TFieldIterator<UFunction> FunctionIt(InterfaceClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
		{
			Candidates.Add(*FunctionIt);
		}
	}
	for (UClass* TempClass = BP->ParentClass.Get(); TempClass; TempClass = TempClass->GetSuperClass())
	{
		for (const FImplementedInterface& Interface : TempClass->Interfaces)
		{
			for (TFieldIterator<UFunction> FunctionIt(Interface.Class, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
			{
				Candidates.Add(*FunctionIt);
			}
		}
	}
	for (UFunction *Candidate : Candidates)
	{
		if (!Seen.Contains(Candidate))
		{
			Seen.Add(Candidate);
			if (CanBlueprintOverride(BP, Candidate)) Results.Add(Candidate);
		}
	}
	return Results;
}

UClass *WingUtils::GetFunctionClass(UFunction *Func)
{
	UClass *Class = Func->GetOuterUClass();
	UBlueprintGeneratedClass *BPGC = Cast<UBlueprintGeneratedClass>(Class);
	if (BPGC) return BPGC->GetAuthoritativeClass();
	else return Class;
}

FString WingUtils::GetFunctionMenuString(UFunction *Func)
{
	UClass *Class = GetFunctionClass(Func);
	TStringBuilder<256> Result;
	Result.Append(FormatName(Class));
	Result.AppendChar('|');
	Result.Append(FormatName(Func));
	return Result.ToString();
}

// ============================================================
// Reparent validation
// ============================================================

bool WingUtils::CanReparentBlueprint(UClass* CurrentGenerated, UClass* Proposed)
{
	if (!CurrentGenerated || !Proposed) return false;

	UClass* CurrentParent = CurrentGenerated->GetSuperClass();
	if (!CurrentParent) return false;

	// Don't allow parenting to itself or one of its children
	if (Proposed->IsChildOf(CurrentGenerated)) return false;

	// Don't allow parenting to an interface
	if (Proposed->IsChildOf(UInterface::StaticClass())) return false;

	// LevelScriptActor blueprints stay in the LevelScriptActor hierarchy
	if (CurrentParent->IsChildOf(ALevelScriptActor::StaticClass()))
		return Proposed->IsChildOf(ALevelScriptActor::StaticClass());

	// Actor blueprints stay in the Actor hierarchy, but not LevelScriptActor
	if (CurrentParent->IsChildOf(AActor::StaticClass()))
		return Proposed->IsChildOf(AActor::StaticClass()) &&
			!Proposed->IsChildOf(ALevelScriptActor::StaticClass());

	// Component blueprints stay in the ActorComponent hierarchy
	if (CurrentParent->IsChildOf(UActorComponent::StaticClass()))
		return Proposed->IsChildOf(UActorComponent::StaticClass());

	// Anim blueprints stay in the AnimInstance hierarchy
	if (CurrentParent->IsChildOf(UAnimInstance::StaticClass()))
		return Proposed->IsChildOf(UAnimInstance::StaticClass());

	// Widget blueprints stay in the UserWidget hierarchy
	if (CurrentParent->IsChildOf(UUserWidget::StaticClass()))
		return Proposed->IsChildOf(UUserWidget::StaticClass());

	return false;
}

// ============================================================
// Blueprint helpers
// ============================================================

TArray<UEdGraph*> WingUtils::AllGraphs(UBlueprint* BP)
{
	TArray<UEdGraph*> Graphs;
	BP->GetAllGraphs(Graphs);
	return Graphs;
}

TArray<UEdGraphNode*> WingUtils::AllNodes(UBlueprint* BP)
{
	TArray<UEdGraphNode*> Nodes;
	for (UEdGraph* Graph : AllGraphs(BP))
		Nodes.Append(Graph->Nodes);
	return Nodes;
}

TArray<UEdGraphNode*> WingUtils::AllNodes(UEdGraph *Graph)
{
	TArray<UEdGraphNode*> Result;
	Result.Append(Graph->Nodes);
	return Result;
}

TArray<UBlueprint*> WingUtils::GetAncestorBlueprints(UBlueprint *BP, bool OldestFirst)
{
	TArray<UBlueprint*> Blueprints;
	for (UBlueprint* WalkBP = BP; WalkBP; )
	{
		Blueprints.Add(WalkBP);
		WalkBP = UBlueprint::GetBlueprintFromClass(WalkBP->ParentClass);
	}
	if (OldestFirst) Algo::Reverse(Blueprints);
	return Blueprints;
}

UObject *WingUtils::GetGeneratedCDO(UBlueprint *BP)
{
	if (BP->GeneratedClass == nullptr) return nullptr;
	return BP->GeneratedClass->GetDefaultObject();
}

TSet<FName> WingUtils::GetAllInUseNames(UBlueprint *BP)
{
	TSet<FName> Names;
	FBlueprintEditorUtils::GetClassVariableList(BP, Names, true);
	FBlueprintEditorUtils::GetFunctionNameList(BP, Names);
	FBlueprintEditorUtils::GetAllGraphNames(BP, Names);
	FBlueprintEditorUtils::GetSCSVariableNameList(BP, Names);
	FBlueprintEditorUtils::GetImplementingBlueprintsFunctionNameList(BP, Names);
	if (UWidgetBlueprint *WBP = Cast<UWidgetBlueprint>(BP))
	{
		TArray<UWidget*> AllWidgets;
		WBP->WidgetTree->GetAllWidgets(AllWidgets);
		WingUtils::AddNamesToSet(Names, AllWidgets);
	}
	return Names;
}

// ============================================================
// Graph Pin Helpers
// ============================================================

UEdGraphPin* WingUtils::CheckGetPin(UEdGraphNode* Node, FName PinName, WingOut Errors)
{
	for (UEdGraphPin* Pin : Node->Pins)
	{
		if (Pin && Pin->GetFName() == PinName)
		{
			return Pin;
		}
	}
	Errors.Printf(TEXT("ERROR: Pin '%s' no longer exists on node '%s'\n"),
		*WingUtils::ExternalizeID(PinName), *WingUtils::FormatName(Node));
	return nullptr;
}

// ============================================================
// Editor helpers
// ============================================================

IAssetEditorInstance* WingUtils::CheckOpenEditorForAsset(UObject* Asset, WingOut Errors)
{
	const FString AssetPath = Asset ? Asset->GetPathName() : TEXT("null");
	UAssetEditorSubsystem* Sub = GEditor ? GEditor->GetEditorSubsystem<UAssetEditorSubsystem>() : nullptr;
	if (!Sub || !Sub->OpenEditorForAsset(Asset))
	{
		Errors.Printf(TEXT("ERROR: Could not open editor for '%s'\n"), *AssetPath);
		return nullptr;
	}

	IAssetEditorInstance* Editor = Sub->FindEditorForAsset(Asset, false);
	if (!Editor)
	{
		Errors.Printf(TEXT("ERROR: Could not find editor instance for '%s'\n"), *AssetPath);
		return nullptr;
	}

	return Editor;
}

// ============================================================
// Material helpers
// ============================================================

void WingUtils::EnsureMaterialGraph(UMaterial* Material)
{
	if (!Material) return;
	if (!Material->MaterialGraph)
	{
		// In commandlet/headless mode the MaterialGraph is not auto-created.
		// Replicate what the Material Editor does on open (MaterialEditor.cpp:619).
		Material->MaterialGraph = CastChecked<UMaterialGraph>(
			FBlueprintEditorUtils::CreateNewGraph(
				Material, NAME_None,
				UMaterialGraph::StaticClass(),
				UMaterialGraphSchema::StaticClass()));
		Material->MaterialGraph->Material = Material;
		Material->MaterialGraph->RebuildGraph();
	}
}

UMaterial* WingUtils::ReplaceMaterialWithTransientCopy(UMaterial* Material)
{
	if (!Material) return nullptr;

	// Already a preview material — nothing to do.
	if (Material->GetOutermost() == GetTransientPackage())
		return Material;

	// If the material editor has a transient preview copy open, get it
	// via the editor API. This follows the same pattern as Epic's
	// MaterialEditingLibrary (FindMaterialEditorForAsset).
	UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
	IAssetEditorInstance* EditorInstance = Sub ? Sub->FindEditorForAsset(Material, false) : nullptr;
	if (EditorInstance)
	{
		// This is a weird hack.  We know that the IAssetEditorInstance for a material
		// is always going to be an FMaterialEditor, which conforms to IMaterialEditor.
		// If that weren't the case, this unsafe code would crash hard.  However,
		// lots of places in unreal use this same unsafe pattern.
		IMaterialEditor* MatEditor = static_cast<IMaterialEditor*>(EditorInstance);
		UMaterialInterface* Edited = MatEditor->GetMaterialInterface();
		if (UMaterial* EditedMat = Cast<UMaterial>(Edited))
			return EditedMat;
	}

	return Material;
}


// ============================================================
// Anim blueprint helpers
// ============================================================

UAnimationStateMachineGraph* WingUtils::FindStateMachineGraph(UBlueprint* BP, const FString& GraphName)
{
	TArray<UEdGraph*> AllGraphs;
	BP->GetAllGraphs(AllGraphs);
	for (UEdGraph* Graph : AllGraphs)
	{
		if (UAnimationStateMachineGraph* SMGraph = Cast<UAnimationStateMachineGraph>(Graph))
		{
			if (SMGraph->GetName() == GraphName)
			{
				return SMGraph;
			}
		}
	}
	return nullptr;
}

UAnimStateNode* WingUtils::FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName, WingOut Errors)
{
	for (UEdGraphNode* Node : SMGraph->Nodes)
	{
		if (UAnimStateNode* StateNode = Cast<UAnimStateNode>(Node))
		{
			if (StateNode->GetStateName() == StateName)
			{
				return StateNode;
			}
		}
	}
	Errors.Printf(TEXT("ERROR: State '%s' not found in graph '%s'\n"), *StateName, *SMGraph->GetName());
	return nullptr;
}

UAnimStateTransitionNode* WingUtils::FindTransition(UAnimationStateMachineGraph* SMGraph,
	const FString& FromStateName, const FString& ToStateName)
{
	for (UEdGraphNode* Node : SMGraph->Nodes)
	{
		if (UAnimStateTransitionNode* TransNode = Cast<UAnimStateTransitionNode>(Node))
		{
			UAnimStateNode* FromState = Cast<UAnimStateNode>(TransNode->GetPreviousState());
			UAnimStateNode* ToState = Cast<UAnimStateNode>(TransNode->GetNextState());
			if (FromState && ToState &&
				(FromState->GetStateName() == FromStateName) &&
				(ToState->GetStateName() == ToStateName))
			{
				return TransNode;
			}
		}
	}
	return nullptr;
}

// ============================================================
// Support for locating UE Wingman Handlers
// ============================================================

TArray<UClass*> WingUtils::CollectHandlerClasses()
{
	TArray<UClass*> Result;
	for (TObjectIterator<UClass> It; It; ++It)
	{
		UClass* Class = *It;
		if (Class->HasAnyClassFlags(CLASS_Abstract)) continue;
		if (!Class->IsChildOf(UWingHandler::StaticClass())) continue;
		Result.Add(Class);
	}
	Result.Sort([](UClass& A, UClass& B) { return GetHandlerName(&A) < GetHandlerName(&B); });
	return Result;
}

FString WingUtils::GetHandlerName(UClass* HandlerClass)
{
	FString Name = HandlerClass->GetName();
	Name.RemoveFromStart(TEXT("Wing_"));
	return Name;
}

FString WingUtils::GetHandlerGroup(UClass* HandlerClass)
{
	FString Name = HandlerClass->GetName();
	Name.RemoveFromStart(TEXT("Wing_"));
	// Everything before the underscore is the group
	int32 UnderscoreIdx;
	if (Name.FindChar(TEXT('_'), UnderscoreIdx))
		return Name.Left(UnderscoreIdx);
	return Name;
}
