/**
 * Copyright 2022-2025 Theai, Inc. dba Inworld AI
 */

#include "GraphEditor/Details/InworldGraphAssetDetailsCustomization.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "IDetailGroup.h"
#include "Graph/Assets/InworldGraphAsset.h"
#include "GraphEditor.h"
#include "GraphEditor/InworldGraphAssetEditor/EdGraph_InworldGraph.h"
#include "GraphEditor/InworldGraphAssetEditor/EdNode_InworldGraphNode.h"
#include "GraphEditor/InworldGraphAssetEditor/InworldGraphEditorStyle.h"
#include "Graph/Nodes/InworldNode_SubgraphNode.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Images/SImage.h"
#include "Styling/AppStyle.h"

#define LOCTEXT_NAMESPACE "FInworldGraphAssetDetailsCustomization"

TSharedRef<IDetailCustomization> FInworldGraphAssetDetailsCustomization::MakeInstance()
{
	return MakeShareable(new FInworldGraphAssetDetailsCustomization());
}

void FInworldGraphAssetDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
	TArray<TWeakObjectPtr<UObject>> SelectedObjects;
	DetailBuilder.GetObjectsBeingCustomized(SelectedObjects);
	if (SelectedObjects.Num() != 1)
	{
		return;
	}

	GraphAsset = Cast<UInworldGraphAsset>(SelectedObjects[0].Get());
	if (!GraphAsset.IsValid())
	{
		return;
	}

	auto HideIfValid = [&DetailBuilder](const TSharedPtr<IPropertyHandle>& Handle)
	{
		if (Handle.IsValid() && Handle->IsValidHandle())
		{
			DetailBuilder.HideProperty(Handle);
		}
	};

	const TSharedPtr<IPropertyHandle> DescriptionProp =
		DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UInworldGraphAsset, Description));
	const TSharedPtr<IPropertyHandle> ExecutionModeProp =
		DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UInworldGraphAsset, ExecutionMode));
	const TSharedPtr<IPropertyHandle> EnableRemoteConfigProp =
		DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UInworldGraphAsset, EnableRemoteConfig));
	const TSharedPtr<IPropertyHandle> ValidationsProp =
		DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UInworldGraphAsset, Validations));
	const TSharedPtr<IPropertyHandle> RuntimeDataListProp =
		DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UInworldGraphAsset, RuntimeDataList));

	HideIfValid(DescriptionProp);
	HideIfValid(ExecutionModeProp);
	HideIfValid(EnableRemoteConfigProp);
	HideIfValid(ValidationsProp);
	HideIfValid(RuntimeDataListProp);

	IDetailCategoryBuilder& InworldCategory =
		DetailBuilder.EditCategory(TEXT("Inworld"), FText::GetEmpty(), ECategoryPriority::Default);
	if (EnableRemoteConfigProp.IsValid())
	{
		InworldCategory.AddProperty(EnableRemoteConfigProp);
	}
	if (DescriptionProp.IsValid()) 
	{ 
		InworldCategory.AddProperty(DescriptionProp); 
	}
	if (ExecutionModeProp.IsValid()) 
	{ 
		InworldCategory.AddProperty(ExecutionModeProp); 
	}
	if (ValidationsProp.IsValid()) 
	{ 
		InworldCategory.AddProperty(ValidationsProp); 
	}
	if (RuntimeDataListProp.IsValid())
	{
		InworldCategory.AddProperty(RuntimeDataListProp);
	}

	IDetailGroup& RuntimeDataGroup = InworldCategory.AddGroup(TEXT("RuntimeDataListGroup"),
										LOCTEXT("RuntimeDataListGroupLabel", "Runtime Data List"));

	TSharedRef<SHorizontalBox> HeaderContent = SNew(SHorizontalBox);

	RuntimeDataGroup.HeaderRow()
		.NameContent()
		[
			HeaderContent
		]
		.ValueContent()
		[
			SNew(STextBlock)
			.Font(IDetailLayoutBuilder::GetDetailFontBold())
			.Text_Lambda([Asset = GraphAsset]()
			{
				int32 ElementsCount = 0;
				if (Asset.IsValid())
				{
					TSet<FString> UniquePairs;
					for (const FInworldGraphRuntimeDataItem& Item : Asset->RuntimeDataList)
					{
						UniquePairs.Add(Item.Key + TEXT("|") + Item.Type);
					}
					ElementsCount = UniquePairs.Num();
				}
				return FText::Format(
					LOCTEXT("RuntimeDataListHeaderFmt", "{0} elements"),
					FText::AsNumber(ElementsCount));
			})
		];

	struct FFInworldGraphRuntimeDataGroupedItem
	{
		FString Key;
		FString Type;
		TArray<UInworldNodeAsset*> Nodes;
	};

	TArray<FFInworldGraphRuntimeDataGroupedItem> GroupedItems;
	TMap<FString, int32> PairToIndex;

	for (const FInworldGraphRuntimeDataItem& Item : GraphAsset->RuntimeDataList)
	{
		const FString PairKey = Item.Key + TEXT("|") + Item.Type;
		int32* FoundIndex = PairToIndex.Find(PairKey);
		if (!FoundIndex)
		{
			const int32 NewIndex = GroupedItems.Add({Item.Key, Item.Type, {}});
			PairToIndex.Add(PairKey, NewIndex);
			FoundIndex = PairToIndex.Find(PairKey);
		}
		if (IsValid(Item.NodePtr))
		{
			GroupedItems[*FoundIndex].Nodes.Add(Item.NodePtr);
		}
	}
	
	TMap<FString, TSet<FString>> KeyToTypes;
	for (const FFInworldGraphRuntimeDataGroupedItem& Group : GroupedItems)
	{
		KeyToTypes.FindOrAdd(Group.Key).Add(Group.Type);
	}
	TSet<FString> KeysWithMultipleTypes;
	for (const auto& Pair : KeyToTypes)
	{
		if (Pair.Value.Num() > 1)
		{
			KeysWithMultipleTypes.Add(Pair.Key);
		}
	}

	bool bWarns{false};
	for (const FFInworldGraphRuntimeDataGroupedItem& Group : GroupedItems)
	{
		const FText KeyText = FText::FromString(Group.Key);
		const FText TypeText = FText::FromString(Group.Type);
		const bool bWarn = Group.Type.IsEmpty() || KeysWithMultipleTypes.Contains(Group.Key);
		FText WarningTooltip;
		{
			FString TooltipStr;
			if (Group.Type.IsEmpty())
			{
				if (!TooltipStr.IsEmpty()) TooltipStr += TEXT("\n");
				TooltipStr += TEXT("The data type was not selected.");
			}
			if (KeysWithMultipleTypes.Contains(Group.Key))
			{
				if (!TooltipStr.IsEmpty()) TooltipStr += TEXT("\n");
				TooltipStr += TEXT("It is not allowed to have one key for multiple data types.");
			}
			WarningTooltip = TooltipStr.IsEmpty() ? FText() : FText::FromString(TooltipStr);
		}

		TSharedRef<SHorizontalBox> KeyContent = SNew(SHorizontalBox);
		
		if (bWarn)
		{
			bWarns = true;
			KeyContent->AddSlot()
				.AutoWidth()
				.Padding(FMargin(2.f, 5.f, 8.f, 5.f))
				[
					SNew(SImage)
					.Image(FInworldGraphEditorStyle::Get()->GetBrush("Inworld.Graph.Warning16"))
					.ToolTipText(WarningTooltip)
				];
		}
		
		KeyContent->AddSlot()
		.FillWidth(1.0f)
		[
			SNew(SEditableTextBox)
			.Text(KeyText)
			.IsReadOnly(true)
			.Font(IDetailLayoutBuilder::GetDetailFont())
			.ToolTipText(WarningTooltip)
		];

		for (int32 Index = 0; Index < Group.Nodes.Num(); ++Index)
		{
			UInworldNodeAsset* Node = Group.Nodes[Index];
			KeyContent->AddSlot()
			.AutoWidth()
			.Padding(FMargin(2.f, 0.f, 0.f, 0.f))
			[
				SNew(SButton)
				.ButtonStyle(&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("SimpleButton"))
				.ContentPadding(FMargin(2.f))
				.HAlign(HAlign_Center)
				.VAlign(VAlign_Center)
				.OnClicked_Lambda([Graph = GraphAsset, Node]() -> FReply
				{
					if (!Graph.IsValid() || !IsValid(Graph->EdGraph) || !IsValid(Node))
					{
						return FReply::Unhandled();
					}
					if (UEdGraph_InworldGraph* EdGraph = Cast<UEdGraph_InworldGraph>(Graph->EdGraph))
					{
						if (UEdNode_InworldGraphNode* FoundPtr = *EdGraph->NodeMap.Find(Node))
						{
							if (UEdNode_InworldGraphNode* EdNode = FoundPtr)
							{
								if (TSharedPtr<SGraphEditor> GraphEd = SGraphEditor::FindGraphEditorForGraph(EdGraph))
								{
									GraphEd->ClearSelectionSet();
									GraphEd->SetNodeSelection(EdNode, true);
									GraphEd->JumpToNode(EdNode);
									return FReply::Handled();
								}
							}
						}
					}
					return FReply::Unhandled();
				})
				[
					SNew(SImage)
					.Image_Lambda([Node]() -> const FSlateBrush*
					{
						if (IsValid(Node) && IsValid(Node->RuntimeNode) &&
							Node->RuntimeNode->IsA(UInworldNode_SubgraphNode::StaticClass()))
						{
							return FInworldGraphEditorStyle::Get()->GetBrush("Inworld.Graph.SearchSubgraph");
						}
						return FInworldGraphEditorStyle::Get()->GetBrush("Inworld.Graph.SearchGlass");
					})
					.ColorAndOpacity(FSlateColor::UseForeground())
				]
			];
		}

		RuntimeDataGroup.AddWidgetRow()
			.NameContent()
			[
				KeyContent
			]
			.ValueContent()
			[
				SNew(SEditableTextBox)
				.Text(TypeText)
				.IsReadOnly(true)
				.Font(IDetailLayoutBuilder::GetDetailFont())
			];
	}
	
	if (bWarns)
	{
		HeaderContent->AddSlot()
			.AutoWidth()
			.Padding(FMargin(2.f, 0.f, 8.f, 0.f))
			[
				SNew(SImage)
				.Image(FInworldGraphEditorStyle::Get()->GetBrush("Inworld.Graph.Warning16"))
			];
	}

	HeaderContent->AddSlot()
		.AutoWidth()
		[
			SNew(STextBlock)
			.Font(IDetailLayoutBuilder::GetDetailFontBold())
			.Text(LOCTEXT("RuntimeDataListHeaderLabel", "Runtime Data List"))
		];
}
#undef LOCTEXT_NAMESPACE
