#pragma once

#include "CoreMinimal.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "Materials/MaterialExpression.h"
#include "Components/ActorComponent.h"
#include "Engine/MemberReference.h"
#include "Engine/Blueprint.h"
#include "K2Node_EditablePinBase.h"
#include "Components/Widget.h"
#include "WingVariables.h"
#include "WingBasics.h"

struct FEdGraphSchemaAction;
class UAnimationStateMachineGraph;
class UAnimSequence;
class UAnimStateNode;
class UAnimStateTransitionNode;
class UBlendSpace;
class USCS_Node;
class IPropertyHandle;
class UScriptStruct;
class UEnum;
class IAssetEditorInstance;
class UMaterial;
class UMaterialFunction;
class UMaterialInstance;
class UStaticMesh;
class USkeletalMesh;
class UTexture;
class UWorld;
struct FBPInterfaceDescription;
struct FWingProperty;
class IPropertyHandle;

// Stateless utility functions used by MCP handlers and the MCP server.
// This is effectively a namespace — all methods are static.
class WingUtils
{
public:
	////////////////////////////////////////////////////////
	//
	// GetFName
	//
	// Get the FName of anything that has an FName.  This
	// is here because having a consistent way to get the FName
	// of something makes it possible to write search templates
	// that work on any of these types.
	//
	////////////////////////////////////////////////////////

	static FName GetFName(const UActorComponent *C) { return C->GetFName(); }
	static FName GetFName(const UEdGraph *Graph) { return Graph->GetFName(); }
	static FName GetFName(const TObjectPtr<UEdGraph> &Graph) { return Graph->GetFName(); }
	static FName GetFName(const UEdGraphNode* Node) { return Node->GetFName(); }
	static FName GetFName(const UEdGraphPin *Pin) { return Pin->GetFName(); }
	static FName GetFName(const FMemberReference &Ref) { return Ref.GetMemberName(); }
	static FName GetFName(const FBPVariableDescription &Var) { return Var.VarName; }
	static FName GetFName(const UMaterialExpression *Expression) { return Expression->GetFName(); }
	static FName GetFName(const FProperty *Prop) { return Prop->GetFName(); }
	static FName GetFName(const FWingProperty &Prop);
	static FName GetFName(const FUserPinInfo &Pin) { return Pin.PinName; }
	static FName GetFName(const TSharedPtr<FUserPinInfo> &Pin) { return Pin->PinName; }
	static FName GetFName(const UWingComponentRef *Ref) { return Ref->VariableName; }
	static FName GetFName(const TStrongObjectPtr<UWingComponentRef> &Ref) { return Ref->VariableName; }
	static FName GetFName(const UWidget *Widget) { return Widget->GetFName(); }
	static FName GetFName(const WingVariables::Var &Var) { return Var.Name; }

	////////////////////////////////////////////////////////
	//
	// When searching an array of structs, you want to
	// return a pointer to one of the structs.  When
	// searching an array of pointers, you want to return
	// one of the pointers.  When searching an array of
	// smart pointers, you want to return one of the smart
	// pointers. The AsPointerLike helper below makes it
	// easy for the search templates below to do that.
	//
	////////////////////////////////////////////////////////

	template<class T> struct IsPointerLike : std::false_type {};
	template<class T> struct IsPointerLike<T*> : std::true_type {};
	template<class T> struct IsPointerLike<TSharedPtr<T>> : std::true_type {};
	template<class T> struct IsPointerLike<TWeakObjectPtr<T>> : std::true_type {};
	template<class T> struct IsPointerLike<TObjectPtr<T>> : std::true_type {};
	template<class T> struct IsPointerLike<TStrongObjectPtr<T>> : std::true_type {};

	template<class T>
	static decltype(auto) AsPointerLike(T&& Elt)
	{
		if constexpr (IsPointerLike<std::remove_cvref_t<T>>::value)
		{
			return std::remove_cvref_t<T>(Elt);
		}
		else
		{
			return &Elt;
		}
	}

	////////////////////////////////////////////////////////
	//
	// Search Templates.  Search an array for an item with a
	// given name.  Works correctly no matter how a name is
	// expressed: eg, "A+B" finds the same results as
	// "A&plus;B". Handles failure cases correctly: two
	// items with the same name, no such item, and malformed
	// names.  In case of failure, sends readable and
	// informative error messages to the AI Agent.
	//
	// Try to direct all searching through these templates,
	// given the amount of care that was put into ensuring
	// their correctness and the quality of their error
	// reporting.
	//
	////////////////////////////////////////////////////////

	template<typename ArrayType>
	static auto FindOneWithInternalID(FName InternalID, ArrayType &&Array, const TCHAR *Kind, WingOut Errors)
	{
		decltype(AsPointerLike(Array[0])) Result = nullptr;
		int Count = 0;
		for (auto &Elt : Array) if (GetFName(Elt) == InternalID) { Count++; Result = AsPointerLike(Elt); }
		if (!CheckExactlyOneNamed(Count, Kind, InternalID, Errors)) Result = nullptr;
		return Result;
	}

	template<typename ArrayType>
	static auto FindOneWithExternalID(const FString &ExternalID, ArrayType &&Array, const TCHAR *Kind, WingOut Errors)
	{
		decltype(AsPointerLike(Array[0])) Result = nullptr;
		FName InternalID = CheckInternalizeID(ExternalID, Errors);
		if (!InternalID.IsNone()) Result = FindOneWithInternalID(InternalID, Array, Kind, Errors);
		return Result;
	}

	template<typename ArrayType>
	static void AddNamesToSet(TSet<FName> &Collection, ArrayType &&Array)
	{
		for (auto &Elt : Array) Collection.Add(GetFName(Elt));
	}

	static bool FindNoDuplicateName(TSet<FName> &Collection, FName InternalID, const TCHAR *Kind, WingOut Errors)
	{
		if (Collection.Contains(InternalID))
			{ CheckExactlyOneNamed(2, Kind, InternalID, Errors); return false; }
		Collection.Add(InternalID);
		return true;
	}

	template<typename ArrayType>
	static bool FindNoDuplicateNames(TSet<FName> &Collection, ArrayType &&Array, const TCHAR *Kind, WingOut Errors)
	{
		for (auto &Elt : Array)
			if (!FindNoDuplicateName(Collection, GetFName(Elt), Kind, Errors)) return false;
		return true;
	}

	////////////////////////////////////////////////////////
	//
	// Name Formatting
	//
	// Get the name of something, and format it for transmission
	// to the AI Agent.  These correctly handle all escaping and
	// quoting.
	//
	////////////////////////////////////////////////////////

	static FString FormatName(const UWorld *World);
	static FString FormatName(const UBlueprint *BP);
	static FString FormatName(const UActorComponent *C);
	static FString FormatName(const USCS_Node *Node);
	static FString FormatName(const UEdGraph *Graph);
	static FString FormatName(const TObjectPtr<UEdGraph> &Graph);
	static FString FormatName(const UEdGraphNode* Node);
	static FString FormatName(const UEdGraphPin *Pin);
	static FString FormatName(const FMemberReference &Ref);
	static FString FormatName(const FBPVariableDescription &Var);
	static FString FormatName(const UStruct *Struct);
	static FString FormatName(const UClass *Class);
	static FString FormatName(const UMaterial *Material);
	static FString FormatName(const UMaterialInstance *MaterialInstance);
	static FString FormatName(const UMaterialFunction *MaterialFunction);
	static FString FormatName(const UMaterialExpression *Expression);
	static FString FormatName(const UStaticMesh *Mesh);
	static FString FormatName(const USkeletalMesh *Mesh);
	static FString FormatName(const UAnimSequence *Anim);
	static FString FormatName(const UBlendSpace *BlendSpace);
	static FString FormatName(const UTexture *Texture);
	static FString FormatName(const UScriptStruct *Struct);
	static FString FormatName(const UEnum *Enum);
	static FString FormatName(const FProperty *Prop);
	static FString FormatName(IPropertyHandle &Handle);
	static FString FormatName(const TSharedPtr<IPropertyHandle> &Handle);
	static FString FormatName(const FUserPinInfo &Pin);
	static FString FormatName(const FBPInterfaceDescription &IFace);
	static FString FormatName(const UWingComponentRef *Ref);
	static FString FormatName(const UWidget *Widget);

	////////////////////////////////////////////////////////
	// static FString ExternalizeID(const FString& InternalID);
	static FString ExternalizeID(FName Name);

	static FName CheckInternalizeID(const FString &ExternalID, WingOut Errors);

	////////////////////////////////////////////////////////
	// In Unreal, Menu items tend to be an unpredictable
	// mix of CamelCase without spaces, and with spaces.
	// In order to make it so that the LLM doesn't have to remember
	// which ones have spaces and which ones don't, we standardize
	// them all to camelcase without spaces.
	////////////////////////////////////////////////////////
	static FString StandardizeMenuItem(const FString &Item);

	////////////////////////////////////////////////////////
	// This routine is used when the LLM is proposing a new
	// name in order to create a new graph, new node, or
	// something like that.  This verifies that the name is
	// a valid external ID, it converts it to an internal
	// ID, and it also verifies that it's a readable
	// internal ID, and that it is not already in use.  
	// If anything goes wrong, it prints an error and
	// returns empty string.
	////////////////////////////////////////////////////////

	static FName CheckProposedName(const FString &Name, const TSet<FName> &InUse, WingOut Errors);
	
	static FString FormatNodeTitle(const UEdGraphNode *Node);

	// ----- Enum helpers -----
	static FString EnumToString(UEnum* Enum, int64 Value);
	static bool StringToEnum(UEnum* Enum, const FString& Str, int64& OutValue, WingOut Errors);

	template<typename T>
	static FString EnumToString(TEnumAsByte<T> Value)
	{ return EnumToString(StaticEnum<T>(), (int64)Value); }

	template<typename T>
	static FString EnumToString(T Value)
	{ return EnumToString(StaticEnum<T>(), (int64)Value); }

	template<typename T>
	static bool StringToEnum(const FString& Str, T& OutValue, WingOut Errors)
	{ int64 V; if (!StringToEnum(StaticEnum<T>(), Str, V, Errors)) return false; OutValue = (T)V; return true; }

	// ----- Blueprint helpers -----
	static TArray<UEdGraph*> AllGraphs(UBlueprint* BP);
	static TArray<UEdGraphNode*> AllNodes(UBlueprint* BP);
	static TArray<UEdGraphNode*> AllNodes(UEdGraph *Graph);
	static TArray<UBlueprint*> GetAncestorBlueprints(UBlueprint *BP, bool OldestFirst = false);
	static UObject *GetGeneratedCDO(UBlueprint *BP);
	static TSet<FName> GetAllInUseNames(UBlueprint *BP);

	// ----- Graph pin helpers -----
	static UEdGraphPin* CheckGetPin(UEdGraphNode* Node, FName PinName, WingOut Errors);

	// ----- Editor helpers -----
	static IAssetEditorInstance* CheckOpenEditorForAsset(UObject* Asset, WingOut Errors);
	
	// ----- Material helpers -----
	static void EnsureMaterialGraph(UMaterial* Material);

	// If the material editor has a transient preview copy of this material,
	// return that copy (which is what the editor is actually working on).
	// Otherwise return the original.
	static UMaterial* ReplaceMaterialWithTransientCopy(UMaterial* Material);

	// ----- Anim blueprint helpers -----
	static UAnimationStateMachineGraph* FindStateMachineGraph(UBlueprint* BP, const FString& GraphName);
	static UAnimStateNode* FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName, WingOut Errors);
	static UAnimStateTransitionNode* FindTransition(UAnimationStateMachineGraph* SMGraph, const FString& FromStateName, const FString& ToStateName);

	// ----- Text formatting -----
	static FString WrapText(const FString& Text, int32 ColLimit, const FString& Prefix);

	// ----- Handler discovery -----
	static TArray<UClass*> CollectHandlerClasses();
	static FString GetHandlerName(UClass* HandlerClass);
	static FString GetHandlerGroup(UClass* HandlerClass);

	// ----- Blueprint override helpers -----
	static bool CanBlueprintOverride(UBlueprint* BP, const UFunction* Function);
	static TArray<UFunction*> GetOverridableFunctions(UBlueprint *BP);
	static UClass *GetFunctionClass(UFunction *Func);
	static FString GetFunctionMenuString(UFunction *Func);

	// ----- Reparent validation -----
	static bool CanReparentBlueprint(UClass* CurrentGenerated, UClass* Proposed);
	
	// ----- Common Error Reporting -----
	static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, const FString &Name, WingOut Errors);
	static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, FName Name, WingOut Errors);
	static bool CheckCanRename(UEdGraphNode* Node, const FString &Name, WingOut Errors);
	static bool CheckNewObjectNotNull(UObject *Obj, const TCHAR *Kind, WingOut Errors);
};
