#include "WingWidgets.h"
#include "WingUtils.h"
#include "WidgetBlueprint.h"
#include "Components/Widget.h"
#include "Components/PanelWidget.h"
#include "Components/PanelSlot.h"
#include "UObject/UObjectIterator.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/IAssetRegistry.h"

FString WingWidgets::WidgetMenuString(UClass* WidgetClass)
{
	if (UObject* GeneratedBy = WidgetClass->ClassGeneratedBy)
		return GeneratedBy->GetPathName();
	UWidget* CDO = WidgetClass->GetDefaultObject<UWidget>();
	FString Category = CDO->GetPaletteCategory().ToString();
	FString Name = WidgetClass->GetName();
	return WingUtils::StandardizeMenuItem(Category + TEXT("|") + Name);
}

FString WingWidgets::WidgetMenuString(const FAssetData& Data)
{
	return Data.GetObjectPathString();
}

WingWidgets::WingWidgets()
{
	TSortedMap<FString, Type> Sorted;

	// Collect loaded native widget classes.
	for (TObjectIterator<UClass> It; It; ++It)
	{
		UClass* Class = *It;
		if (!Class->IsChildOf(UWidget::StaticClass())) continue;
		if (Class->HasAnyClassFlags(CLASS_Abstract | CLASS_Deprecated | CLASS_NewerVersionExists | CLASS_Hidden)) continue;
		if (Class->HasAnyFlags(RF_Transient) && Class->HasAnyClassFlags(CLASS_CompiledFromBlueprint)) continue;

		FString MenuName = WidgetMenuString(Class);
		Sorted.Add(MenuName, { MenuName, Class });
	}

	// Collect unloaded Widget Blueprint assets from the AssetRegistry.
	IAssetRegistry& Registry = *IAssetRegistry::Get();
	TArray<FAssetData> AssetResults;
	Registry.GetAssetsByClass(UWidgetBlueprint::StaticClass()->GetClassPathName(), AssetResults, true);
	for (const FAssetData& Data : AssetResults)
	{
		if (Data.IsAssetLoaded()) continue;

		FString GeneratedClassPath;
		if (!Data.GetTagValue(FBlueprintTags::GeneratedClassPath, GeneratedClassPath)) continue;

		FString MenuName = WidgetMenuString(Data);
		Type Entry;
		Entry.MenuName = MenuName;
		Entry.Class = TSoftClassPtr<UWidget>(FSoftClassPath(GeneratedClassPath));
		Sorted.Add(MenuName, Entry);
	}

	// Flatten into the array.
	AllWidgets.Reserve(Sorted.Num());
	for (const auto& Pair : Sorted)
	{
		AllWidgets.Add(Pair.Value);
	}
}

TArray<WingWidgets::Type> WingWidgets::Search(const FString& Query, int32 MaxResults, bool Exact)
{
	FString ExtQuery = FString::Printf(TEXT("*%s*"), *Query.Replace(TEXT(" "), TEXT("*")));
	TArray<Type> Results;
	for (const Type& Entry : AllWidgets)
	{
		if (Results.Num() >= MaxResults) break;
		if (Exact)
		{
			if (Entry.MenuName.Equals(Query, ESearchCase::IgnoreCase))
				Results.Add(Entry);
		}
		else
		{
			if (Entry.MenuName.MatchesWildcard(ExtQuery, ESearchCase::IgnoreCase))
				Results.Add(Entry);
		}
	}
	return Results;
}

bool WingWidgets::CheckCanBeParent(UWidget* Widget, WingOut Errors)
{
	UPanelWidget* Panel = Cast<UPanelWidget>(Widget);
	if (!Panel)
	{
		Errors.Printf(TEXT("ERROR: '%s' is not a panel widget and cannot have children\n"), *WingUtils::FormatName(Widget));
		return false;
	}
	if (!Panel->CanAddMoreChildren())
	{
		Errors.Printf(TEXT("ERROR: '%s' already has a child and cannot accept more\n"), *WingUtils::FormatName(Widget));
		return false;
	}
	return true;
}

void WingWidgets::PrintWidgetTree(UWidget* Widget, int32 Depth)
{
	FString Indent = FString::ChrN(Depth * 2, TEXT(' '));
	if (Widget == nullptr)
	{
		WingOut::Stdout.Printf(TEXT("%s[null]\n"), *Indent);
		return;
	}
	FString TypeName = WingUtils::FormatName(Widget->GetClass());
	FString WidgetName = WingUtils::FormatName(Widget);
	WingOut::Stdout.Printf(TEXT("%s%s %s\n"), *Indent, *TypeName, *WidgetName);

	if (UPanelWidget* Panel = Cast<UPanelWidget>(Widget))
	{
		for (UPanelSlot* Slot : Panel->GetSlots())
			PrintWidgetTree(Slot->Content, Depth + 1);
	}
}
