#include "WingToolMenu.h"
#include "WingHacks.h"
#include "ToolMenuEntry.h"
#include "ToolMenuDelegates.h"
#include "ToolMenuContext.h"
#include "ToolMenu.h"
#include "ToolMenuSection.h"
#include "WingUtils.h"
#include "EdGraphSchema_K2.h"
#include "Framework/Commands/UIAction.h"

// ============================================================
// Given a menu entry label, and a pin name (possibly empty),
// generate a better label for an LLM to type.  The goal here
// is to have consistent spacing, consistent casing, so that
// the LLM can easily remember what to type.
// ============================================================

FText WingToolMenu::MakeBetterLabel(const UEdGraphPin *Pin, const FText &EntryLabel)
{
	FString Standardized = WingUtils::StandardizeMenuItem(EntryLabel.ToString());
	if (Pin)
	{
		Standardized = FString::Printf(TEXT("Pin %s %s"),
			*WingUtils::FormatName(Pin), *Standardized);
	}
	return FText::FromString(Standardized);
}

// ============================================================
// Check if an array of entries contains a specific label.
// ============================================================

bool WingToolMenu::ContainsText(const TArray<FText> &Texts, const FText &Value)
{
	for (const FText &Text : Texts)
	{
		if (Value.IdenticalTo(Text))
		{
			return true;
		}
	}
	return false;
}

// ============================================================
// AddEntry — create a synthetic menu entry with a direct action.
// ============================================================

void WingToolMenu::AddEntry(TArray<FToolMenuEntry>& Entries, UEdGraphPin* Pin,
	const TCHAR* Label, FCanExecuteAction CanExec, FExecuteAction Exec)
{
	if (!CanExec.Execute())
		return;
	FToolMenuEntry Entry = FToolMenuEntry::InitMenuEntry(
		NAME_None,
		MakeBetterLabel(Pin, FText::FromString(Label)),
		FText::GetEmpty(),
		FSlateIcon(),
		FUIAction(MoveTemp(Exec), MoveTemp(CanExec)));
	Entries.Add(MoveTemp(Entry));
}

void WingToolMenu::AddSyntheticEntries(TArray<FToolMenuEntry> &Entries, UEdGraphNode *NodePtr)
{
	const UEdGraphSchema_K2 *K2Schema = Cast<UEdGraphSchema_K2>(NodePtr->GetSchema());
	if (K2Schema == nullptr) return;
	// TWeakObjectPtr<UEdGraphNode> Node(NodePtr);
	for (UEdGraphPin *PinPtr : NodePtr->Pins)
	{
		if (PinPtr->bHidden) continue;
		FEdGraphPinReference Pin(PinPtr);
		AddEntry(Entries, PinPtr, TEXT("SplitStructPin"),
			[=](){ UEdGraphPin *P=Pin.Get(); return P && K2Schema->CanSplitStructPin(*P); },
			[=](){ UEdGraphPin *P=Pin.Get(); if (P) K2Schema->SplitPin(P); });
		AddEntry(Entries, PinPtr, TEXT("RecombineStructPin"),
			[=](){ UEdGraphPin *P=Pin.Get(); return P && K2Schema->CanRecombineStructPin(*P); },
			[=](){ UEdGraphPin *P=Pin.Get(); if (P) K2Schema->RecombinePin(P); });
	}
}

// ============================================================
// Get the Menu Items for a given node and pin.  This doesn't
// do anything with the labels yet.
// ============================================================

TArray<FToolMenuEntry> WingToolMenu::GetMenuItems(
		UGraphNodeContextMenuContext* GNC, const FToolMenuContext &TMC)
{
	TArray<FToolMenuEntry> Result;
	UToolMenu* Menu = NewObject<UToolMenu>();
	GNC->Node->GetNodeContextMenuActions(Menu, GNC);
	//GNC->Node->GetSchema()->GetContextMenuActions(Menu, GNC);
	for (FToolMenuSection& Section : Menu->Sections)
	{
		Result.Append(Section.Blocks);
	}
	return Result;
}

TArray<FToolMenuEntry> WingToolMenu::GetMenuItems(UEdGraphNode *Node, const FToolMenuContext &Context)
{
	// Create the two context objects.
	TArray<FToolMenuEntry> Result;
	UGraphNodeContextMenuContext* GNC = NewObject<UGraphNodeContextMenuContext>();

	// Fetch the menu items for the node.
	GNC->Init(Node->GetGraph(), Node, nullptr, false);
	TArray<FToolMenuEntry> NodeEntries = GetMenuItems(GNC, Context);

	// Improve the labels for the node entries, and also
	// record the original labels.
	TArray<FText> OriginalLabels;
	for (FToolMenuEntry &Entry: NodeEntries)
	{
		FText Label = Entry.Label.Get();
		OriginalLabels.Add(Label);
		if (!CanExecute(Entry, Context)) continue;
		Entry.Label = MakeBetterLabel(nullptr, Label);
		Result.Add(Entry);
	}

	// Fetch the Menu items for the pins. Discard
	// pins whose original label exactly matches the
	// original label of a node entry.
	for (const UEdGraphPin *Pin : Node->Pins)
	{
		if (Pin->bHidden) continue;
		FString PinName = WingUtils::FormatName(Pin);
		GNC->Init(Node->GetGraph(), Node, Pin, false);
		TArray<FToolMenuEntry> PinEntries = GetMenuItems(GNC, Context);
		for (FToolMenuEntry &PinEntry : PinEntries)
		{
			FText Label = PinEntry.Label.Get();
			if (!ContainsText(OriginalLabels, Label))
			{
				if (CanExecute(PinEntry, Context))
				{
					PinEntry.Label = MakeBetterLabel(Pin, Label);
					Result.Add(PinEntry);
				}
			}
		}
	}

	AddSyntheticEntries(Result, Node);
	return Result;
}

// ============================================================
// Menu entry resolution
//
// We only handle the three Action-based callback mechanisms:
//   1. FToolUIAction — delegates that take a FToolMenuContext
//   2. FToolDynamicUIAction — same, Blueprint-friendly variant
//   3. FUIAction — plain delegates, no context needed
//
// Command-based entries are skipped — they depend on editor
// selection/focus state which we can't reliably provide.
// ============================================================

bool WingToolMenu::CanExecute(const FToolMenuEntry& Entry, const FToolMenuContext& Context)
{
	if (WingHacks::HasCommand(Entry))
		return false;

	const FToolUIActionChoice& Choice = WingHacks::GetAction(Entry);

	if (const FToolUIAction* ToolAction = Choice.GetToolUIAction())
	{
		if (ToolAction->CanExecuteAction.IsBound())
			return ToolAction->CanExecuteAction.Execute(Context);
		return ToolAction->ExecuteAction.IsBound();
	}

	if (const FToolDynamicUIAction* DynamicAction = Choice.GetToolDynamicUIAction())
	{
		if (DynamicAction->CanExecuteAction.IsBound())
			return DynamicAction->CanExecuteAction.Execute(Context);
		return DynamicAction->ExecuteAction.IsBound();
	}

	if (const FUIAction* Action = Choice.GetUIAction())
		return Action->IsBound() && Action->CanExecute();

	return false;
}

bool WingToolMenu::Execute(const FToolMenuEntry& Entry, const FToolMenuContext& Context)
{
	if (WingHacks::HasCommand(Entry))
		return false;
	
	const FToolUIActionChoice& Choice = WingHacks::GetAction(Entry);

	if (const FToolUIAction* ToolAction = Choice.GetToolUIAction())
	{
		ToolAction->ExecuteAction.ExecuteIfBound(Context);
		return true;
	}

	if (const FToolDynamicUIAction* DynamicAction = Choice.GetToolDynamicUIAction())
	{
		DynamicAction->ExecuteAction.ExecuteIfBound(Context);
		return true;
	}

	if (const FUIAction* Action = Choice.GetUIAction())
		return Action->Execute();

	return false;
}
