#include "WingGraphActions.h"
#include "AnimNotifyEventNodeSpawner.h"
#include "BlueprintActionFilter.h"
#include "BlueprintActionDatabase.h"
#include "BlueprintBoundEventNodeSpawner.h"
#include "BlueprintComponentNodeSpawner.h"
#include "BlueprintDelegateNodeSpawner.h"
#include "BlueprintEventNodeSpawner.h"
#include "BlueprintFunctionNodeSpawner.h"
#include "BlueprintNodeSpawner.h"
#include "BlueprintVariableNodeSpawner.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2.h"
#include "Animation/AnimInstance.h"
#include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "WingUtils.h"

FName FWingGraphAction::GetPropertyClassName(const FProperty *Prop)
{
	if (Prop == nullptr) return FName();
	UClass *Class = Prop->GetOwnerClass();
	if (Class == nullptr) return FName();
	return Class->GetFName();
}

FName FWingGraphAction::GetSpawnerOwnerClass(const UBlueprintNodeSpawner* Spawner)
{
	if (const UBlueprintFunctionNodeSpawner* FunctionSpawner = Cast<UBlueprintFunctionNodeSpawner>(Spawner))
	{
		return FunctionSpawner->GetFunction()->GetOwnerClass()->GetFName();
	}

	if (const UBlueprintVariableNodeSpawner* VariableSpawner = Cast<UBlueprintVariableNodeSpawner>(Spawner))
	{
		if (VariableSpawner->IsLocalVariable()) return FName(TEXT("LocalVariable"));
		return GetPropertyClassName(VariableSpawner->GetVarProperty());
	}
	
	if (const UBlueprintDelegateNodeSpawner* DelegateSpawner = Cast<UBlueprintDelegateNodeSpawner>(Spawner))
	{
		return GetPropertyClassName(DelegateSpawner->GetDelegateProperty());
	}

	if (const UBlueprintBoundEventNodeSpawner* BoundEventSpawner = Cast<UBlueprintBoundEventNodeSpawner>(Spawner))
	{
		return GetPropertyClassName(BoundEventSpawner->GetEventDelegate());
	}

	if (Cast<UAnimNotifyEventNodeSpawner>(Spawner))
	{
		return UAnimInstance::StaticClass()->GetFName();
	}

	if (const UBlueprintEventNodeSpawner* EventSpawner = Cast<UBlueprintEventNodeSpawner>(Spawner))
	{
		if (EventSpawner->IsForCustomEvent()) return FName();
		return EventSpawner->GetEventFunction()->GetOwnerClass()->GetFName();
	}

	if (const UBlueprintComponentNodeSpawner* ComponentSpawner = Cast<UBlueprintComponentNodeSpawner>(Spawner))
	{
		if (const UClass* ComponentClass = ComponentSpawner->GetComponentClass())
		{
			return ComponentClass->GetFName();
		}
		// todo: get a class name from the asset data.
		return FName();
	}

	return FName();
}

FWingGraphAction::FWingGraphAction(TSharedPtr<FEdGraphSchemaAction> &iAction, UEdGraph *iGraph)
{
	Action = iAction;
	Graph = iGraph;
	FString Category = Action->GetCategory().ToString();
	FString MenuName = Action->GetMenuDescription().ToString();
	Name = WingUtils::StandardizeMenuItem(Category + TEXT("|") + MenuName);
	Keywords = Action->GetKeywords().ToString();
}

FWingGraphAction::FWingGraphAction(UBlueprintNodeSpawner *iSpawner, UEdGraph *iGraph)
{
	Spawner = iSpawner;
	Graph = iGraph;
	const FBlueprintActionUiSpec& UiSpec = Spawner->PrimeDefaultUiSpec();
	Name = UiSpec.Category.ToString() + TEXT("|") + UiSpec.MenuName.ToString();
	Keywords = UiSpec.Keywords.ToString();
	if (FName OwnerClass = GetSpawnerOwnerClass(Spawner); !OwnerClass.IsNone())
	{
		Name = OwnerClass.ToString() + TEXT("|") + Name;
	}
	Name = WingUtils::StandardizeMenuItem(Name);
}

UEdGraphNode* FWingGraphAction::Execute(const FVector2D &Location) const
{
	if (Spawner)
	{
		return Spawner->Invoke(Graph, IBlueprintNodeBinder::FBindingSet(), Location);
	}
	else
	{
		return Action->PerformAction(Graph, nullptr, Location, /*bSelectNewNode=*/false);
	}
}

TArray<FWingGraphAction*> FWingGraphActions::Search(const FString &Query, int32 MaxResults, bool Exact)
{
	FString ExtQuery = FString::Printf(TEXT("*%s*"), *Query.Replace(TEXT(" "), TEXT("*")));
	TArray<FWingGraphAction*> Results;
	for (FWingGraphAction &Result : Actions)
	{
		if (Results.Num() == MaxResults) break;
		if (Exact)
		{
			if (Result.Name.Equals(Query, ESearchCase::IgnoreCase))
				Results.Emplace(&Result);
		}
		else
		{
			if (Result.Name.MatchesWildcard(ExtQuery, ESearchCase::IgnoreCase) ||
				Result.Keywords.MatchesWildcard(ExtQuery, ESearchCase::IgnoreCase))
				Results.Emplace(&Result);
		}
	}
	return Results;
}

void FWingGraphActions::CollectActions()
{
	FGraphContextMenuBuilder ContextMenuBuilder(Graph);
	Graph->GetSchema()->GetGraphContextActions(ContextMenuBuilder);
	for (int32 i = 0; i < ContextMenuBuilder.GetNumActions(); i++)
	{
		Actions.Emplace(ContextMenuBuilder.GetSchemaAction(i), Graph);
	}
}

void FWingGraphActions::CollectSpawners()
{
	UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph);
	if (!Blueprint) return;
	FBlueprintActionContext Context;
	Context.Blueprints.Add(Blueprint);
	Context.Graphs.Add(Graph);

	FBlueprintActionFilter Filter(FBlueprintActionFilter::BPFILTER_NoFlags);
	Filter.Context = Context;

	for (const auto& Pair : FBlueprintActionDatabase::Get().GetAllActions())
	{
		UObject* ActionObject = Pair.Key.ResolveObjectPtr();
		if (!ActionObject) continue;

		for (UBlueprintNodeSpawner* Spawner : Pair.Value)
		{
			if (!Spawner->NodeClass) continue;

			FBlueprintActionInfo ActionInfo(ActionObject, Spawner);
			if (Filter.IsFiltered(ActionInfo)) continue;

			Actions.Emplace(Spawner, Graph);
		}
	}
}

FWingGraphActions::FWingGraphActions(UEdGraph *iGraph)
{
	Graph = iGraph;
	if (Cast<UEdGraphSchema_K2>(Graph->GetSchema()))
	{
		CollectSpawners();
	}
	else
	{
		CollectActions();
	}
}
