// Copyright (c) 2023 Codemongous LLC. All Rights Reserved.

#include "SSourcifyGraph.h"

#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetToolsModule.h"
#include "CollectionManagerModule.h"
#include "ContentBrowserModule.h"
#include "DesktopPlatformModule.h"
#include "Dialogs/Dialogs.h"
#include "EdGraph_Sourcify.h"
#include "EdGraphNode_Sourcify.h"
#include "Editor.h"
#include "EditorWidgetsModule.h"
#include "Engine/AssetManager.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "FileHelpers.h"
#include "Framework/Views/TableViewMetadata.h"
#include "GameFramework/Actor.h"
#include "GameProjectUtils.h"
#include "HAL/PlatformApplicationMisc.h"
#include "ICollectionManager.h"
#include "IContentBrowserSingleton.h"
#include "IDesktopPlatform.h"
#include "ISettingsModule.h"
#include "Misc/MessageDialog.h"
#include "Misc/PackageName.h"
#include "Misc/ScopedSlowTask.h"
#include "ModuleDescriptor.h"
#include "Modules/ModuleManager.h"
#include "ObjectTools.h"
#include "PropertyCustomizationHelpers.h"
#include "Selection.h"
#include "SGraphPanel.h"
#include "SourceCodeNavigation.h"
#include "SourceControlHelpers.h"
#include "SourceControlOperations.h"
#include "ISourceControlModule.h"
#include "ISourceControlProvider.h"
#include "Sourcify.h"
#include "SourcifyCoreUEWrapperModule.h"
#include "SourcifyEditorCommands.h"
#include "SourcifyGraphSchema.h"
#include "SourcifyGraphStyle.h"
#include "SourcifySettings.h"
#include "SSourcifyNode.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "SWarningOrErrorBox.h"
#include "Toolkits/GlobalEditorCommonCommands.h"
#include "UI/SSourcifyExpanderArrow.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Images/SThrobber.h"
#include "Widgets/Input/SComboBox.h"
#include "Widgets/Input/SDirectoryPicker.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Input/SSpinBox.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Layout/SSplitter.h"
#include "Widgets/Notifications/SProgressBar.h"
#include "Widgets/Views/SListView.h"
#include "Async/Async.h"

#define LOCTEXT_NAMESPACE "SourcifyGraph"

using namespace Sourcify;

namespace SSourcifyGraphInternal
{
	static const FText MultipleValuesSelected = LOCTEXT("MultipleValuesSelected", "<multiple values>");
	static const FText NothingSelected = LOCTEXT("NothingSelected", "<nothing selected>");

	static constexpr int32 AssetThumbnailSize = 32;
	static constexpr float SubRowPaddingLeft = 5.f;
	static constexpr float SubRowPaddingTop = 2.f;
	static constexpr float SubRowPaddingRight = 5.f;
	static constexpr float SubRowPaddingBottom = 2.f;

	FMargin GetRowThumbnailPadding()
	{
		return FMargin(SubRowPaddingLeft, SubRowPaddingTop, SubRowPaddingRight, SubRowPaddingBottom);
	}

	/** Custom widget that draws a rounded orange border for highlighting nodes */
	class SNodeHighlightBorder : public SLeafWidget
	{
	public:
		SLATE_BEGIN_ARGS(SNodeHighlightBorder)
			: _BorderColor(FLinearColor(1.0f, 0.5f, 0.0f, 0.8f)) // Orange
			, _BorderThickness(10.0f)
			, _CornerRadius(8.0f)
		{}
			SLATE_ARGUMENT(FLinearColor, BorderColor)
			SLATE_ARGUMENT(float, BorderThickness)
			SLATE_ARGUMENT(float, CornerRadius)
		SLATE_END_ARGS()

		void Construct(const FArguments& InArgs)
		{
			BorderColor = InArgs._BorderColor;
			BorderThickness = InArgs._BorderThickness;
			CornerRadius = InArgs._CornerRadius;
		}

		virtual FVector2D ComputeDesiredSize(float) const override
		{
			return FVector2D::ZeroVector;
		}

		virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override
		{
			const FVector2D LocalSize = AllottedGeometry.GetLocalSize();
			
			// Use lines to draw a rounded rectangle border
			TArray<FVector2D> Points;
			const float Radius = FMath::Min(CornerRadius, FMath::Min(LocalSize.X, LocalSize.Y) * 0.5f);
			const int32 NumSegmentsPerCorner = 8;
			
			// Generate points for rounded rectangle
			auto AddCornerPoints = [&Points, NumSegmentsPerCorner, Radius](const FVector2D& Center, float StartAngle)
			{
				for (int32 i = 0; i <= NumSegmentsPerCorner; ++i)
				{
					float Angle = StartAngle + (PI * 0.5f * i / NumSegmentsPerCorner);
					Points.Add(Center + FVector2D(FMath::Cos(Angle), FMath::Sin(Angle)) * Radius);
				}
			};
			
			// Top-left corner
			FVector2D TopLeftCornerPos = FVector2D(Radius, Radius);
			float TopLeftStartAngle = PI;
			AddCornerPoints(TopLeftCornerPos, TopLeftStartAngle);
			// Top-right corner
			AddCornerPoints(FVector2D(LocalSize.X - Radius, Radius), -PI * 0.5f);
			// Bottom-right corner
			AddCornerPoints(FVector2D(LocalSize.X - Radius, LocalSize.Y - Radius), 0.0f);
			// Bottom-left corner
			AddCornerPoints(FVector2D(Radius, LocalSize.Y - Radius), PI * 0.5f);
			// Close the path
			AddCornerPoints(TopLeftCornerPos, TopLeftStartAngle);
			
			// Draw the border as lines
			FSlateDrawElement::MakeLines(
				OutDrawElements,
				LayerId,
				AllottedGeometry.ToPaintGeometry(),
				Points,
				ESlateDrawEffect::None,
				BorderColor,
				true, // bAntialias
				BorderThickness
			);

			return LayerId + 1;
		}

	private:
		FLinearColor BorderColor;
		float BorderThickness;
		float CornerRadius;
	};
}

SSourcifyGraph::~SSourcifyGraph()
{
	if (!GExitPurge)
	{
		if (ensure(GraphObj))
		{
			GraphObj->RemoveFromRoot();
		}
	}
}

void SSourcifyGraph::Construct(const FArguments& InArgs)
{
	bNeedsGraphRebuild = false;
	
	// Create an action list and register commands
	RegisterActions();

	ParentTab = InArgs._OwnerTab;

	FSourcifyModule& SourcifyModule = FModuleManager::LoadModuleChecked<FSourcifyModule>("Sourcify");
	SourcifyModule.OnSourcifyingComplete.AddSP(this, &SSourcifyGraph::OnSourcifyingComplete);

	// Create the graph
	GraphObj = NewObject<UEdGraph_Sourcify>();
	GraphObj->Schema = USourcifyGraphSchema::StaticClass();
	GraphObj->AddToRoot();
	GraphObj->SetSourcifyGraph(StaticCastSharedRef<SSourcifyGraph>(AsShared()));
	
	SGraphEditor::FGraphEditorEvents GraphEvents;
	GraphEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP(this, &SSourcifyGraph::OnSelectedNodesChanged);
	
	// We don't want an action menu summoned, so register a delegate that returns an empty FActionMenuContent
	
	// AV - 08/30/2025 - Disabled context menu on right-click in empty space
	// was causing a crash because it was returning an empty delegate handler inside OnCreateActionMenu
	// (I think that's why it was crashing, will investigate later)
	//GraphEvents.OnCreateActionMenu = SGraphEditor::FOnCreateActionMenu::CreateSP(this, &SSourcifyGraph::OnCreateActionMenu);

	// Create the graph editor
	GraphEditorPtr = SNew(SGraphEditor)
		.AdditionalCommands(ReferenceViewerActions)
		.GraphToEdit(GraphObj)
		.GraphEvents(GraphEvents)
		.ShowGraphStateOverlay(false);

	SGraphPanel* GraphPanel = GraphEditorPtr->GetGraphPanel();
	
	FEditorWidgetsModule& EditorWidgetsModule = FModuleManager::LoadModuleChecked<FEditorWidgetsModule>("EditorWidgets");
	TSharedRef<SWidget> AssetDiscoveryIndicator = EditorWidgetsModule.CreateAssetDiscoveryIndicator(EAssetDiscoveryIndicatorScaleMode::Scale_None, FMargin(16, 8), false);

	const FSourcifyEditorCommands& UICommands = FSourcifyEditorCommands::Get();

	static const FName DefaultForegroundName("DefaultForeground");

	bool NewVersionAvailable = FSourcifyModule::Get().IsNewVersionAvailable();
	
	TSharedPtr<SButton> OpenButton;
	ChildSlot
	[
		SNew(SVerticalBox)

		// Path and history
		+SVerticalBox::Slot()
		.AutoHeight()
		[
			SNew(SBorder)
			.BorderImage(FAppStyle::GetBrush("Brushes.Panel"))
			[
				SNew(SHorizontalBox)

				+SHorizontalBox::Slot()
				.AutoWidth()
				.Padding(4, 0)
				[
					MakeToolBar()
				]

				// Path
				+SHorizontalBox::Slot()
				.Padding(0, 0, 4, 0)
				.FillWidth(1.f)
				.VAlign(VAlign_Center)
				[
					SNew(SBorder)
					.BorderImage( FAppStyle::GetBrush("ToolPanel.GroupBorder") )
					[
						SNew(SEditableTextBox)
						.IsReadOnly(true)
						.Text(this, &SSourcifyGraph::GetAddressBarText)
						.SelectAllTextWhenFocused(true)
						.SelectAllTextOnCommit(true)
					]
				]
				// Sourcify Button
				+ SHorizontalBox::Slot()
				.Padding(0, 0, 4, 0)
				.AutoWidth()
				.VAlign(VAlign_Center)
				[
					SNew(SButton)
					.ButtonColorAndOpacity_Lambda([this]()
					{

						if (GetSourcifyReadiness() == ReadyState::CanNeverBeReady)
						{
							return FAppStyle::GetColor("FlatButton.Danger");
						}
						if (GetSourcifyReadiness() == ReadyState::NotReady)
						{
							return FAppStyle::GetColor("FlatButton.Warning");
						}

						// GetSourcifyReadiness() == ReadyState::Ready
						return FAppStyle::GetColor("FlatButton.Success");
						
					})
					.ButtonStyle(FAppStyle::Get(), "FlatButton.Success")
					.OnClicked(this, &SSourcifyGraph::OnSourcifyClicked)
					.IsEnabled_Lambda([this]()
						{
							return (GetSourcifyReadiness() == ReadyState::Ready);
						}
					)
					[
						SNew(STextBlock)
							.Text(FText::FromString(TEXT("Sourcify")))
					]
				]
				// Settings Button
				+ SHorizontalBox::Slot()
				.Padding(0, 0, 4, 0)
				.AutoWidth()
				.VAlign(VAlign_Center)
				[
					SNew(SButton)
					.ButtonStyle(FAppStyle::Get(), "SimpleButton")
					.OnClicked(this, &SSourcifyGraph::OnSettingsClicked)
					.ToolTipText(LOCTEXT("SourcifySettingsTooltip", "Open Sourcify Settings"))
					[
						SNew(SImage)
						.Image(FAppStyle::GetBrush("Icons.Settings"))
						.ColorAndOpacity(FSlateColor::UseForeground())
					]
				]
			]
		]

		// Graph Area
		+SVerticalBox::Slot()
		.FillHeight(1.f)
		.HAlign(HAlign_Fill)
		[
			SNew(SSplitter)
			.Orientation(Orient_Horizontal)
			
			// Left side - Graph
			+SSplitter::Slot()
			.Value(0.75f)
			[
				SNew(SOverlay)

				// The graph itself
				+SOverlay::Slot()
				[
					GraphEditorPtr.ToSharedRef()
				]

				// Highlight overlay canvas for showing orange highlights around nodes when hovering over referencers
				+SOverlay::Slot()
				[
					SAssignNew(HighlightOverlayCanvas, SCanvas)
					.Visibility(EVisibility::HitTestInvisible)
				]

				// The info box displayed when one or more nodes is selected
				+SOverlay::Slot()
				.VAlign(VAlign_Top)
				.HAlign(HAlign_Fill)
				[
					SNew(SBorder)
					.BorderImage(FAppStyle::GetBrush("Brushes.Panel"))
					.BorderBackgroundColor(FLinearColor(0.5f, 0.5f, 0.5f, 1.f))
					[
						SNew(SVerticalBox)
						.Visibility_Lambda([this]() { return (IsAnyNodeSelected() ? EVisibility::SelfHitTestInvisible : EVisibility::Collapsed); })
						+ SVerticalBox::Slot()
						.AutoHeight()
						[
							SNew(SHorizontalBox)
							+ SHorizontalBox::Slot()
							.MaxWidth(120)
							.HAlign(HAlign_Fill)
							[
								SNew(STextBlock)
								.Text(LOCTEXT("ResourcifyNameLabelText", "Resourcify"))
								.ToolTipText(LOCTEXT("ResourcifyNameLabelToolTip", "If checked, any new BP only properties, components, functions, events, and delegates will be added to the previously Sourcified base class.\nIf unchecked, a whole new base class will be created"))
								.Visibility_Lambda([this] { return CanResourcifySelectedNodes(); })
							]
							+ SHorizontalBox::Slot()
							.AutoWidth()
							.HAlign(HAlign_Fill)
							[
								SAssignNew(ResourcifyCheckBox, SCheckBox)
									.IsChecked_Lambda([this]() -> ECheckBoxState
										{
											if (SelectedNodes.Num() > 1)
											{
												return ECheckBoxState::Undetermined;
											}
											return SelectedNodes[0]->GetInfo().Node->ConversionSettings.GetResourcify() && SelectedNodes[0]->GetInfo().Node->CanBeResourcified ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
										})
									.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState)
										{
											for (auto Node : SelectedNodes)
											{
												Node->GetInfo().Node->ConversionSettings.SetResourcify(NewState == ECheckBoxState::Checked);
												Node->GetInfo().Node->UpdateReadyState();
												Node->SourcifyWidgetNode->UpdateErrorInfo();
											}

											if (NewState == ECheckBoxState::Unchecked)
											{
												ValidateConversionSettingsForSelected();
											}
										})
									.Visibility_Lambda([this] { return CanResourcifySelectedNodes(); })
							]
						]
						+ SVerticalBox::Slot()
						.AutoHeight()
						[
							SNew(SHorizontalBox)
							+ SHorizontalBox::Slot()
							.AutoWidth()
							.HAlign(HAlign_Left)
							[
								SNew(STextBlock)
								.MinDesiredWidth(120)
								.Text(LOCTEXT("SourceOutputDirectoryLabelText", "Output Directory"))
								.ToolTipText(LOCTEXT("SourceOutputDirectoryToolTip", "The directory where source and header files for this asset will be stored."))
							]
							+ SHorizontalBox::Slot()
							.HAlign(HAlign_Fill)
							.FillWidth(1.0f)
							[
								SNew(SEditableTextBox)
									.Text(this, &SSourcifyGraph::GetOutputDirectoryForSelected)
									.OnTextCommitted_Lambda([this](const FText& NewValue, ETextCommit::Type)
										{
											SetOutputDirectoryForSelected(NewValue);
											ValidateConversionSettingsForSelected();
										}
									)
									.IsEnabled_Lambda([this]() -> bool { return ResourcifyCheckBox->GetCheckedState() != ECheckBoxState::Checked; })
							]
							+ SHorizontalBox::Slot()
							.AutoWidth()
							.Padding(4.0f, 0.0f, 0.0f, 0.0f)
							[
								SAssignNew(OpenButton, SButton)
									.OnClicked(this, &SSourcifyGraph::BrowseForDirectory)
									.IsEnabled_Lambda([this]() -> bool { return ResourcifyCheckBox->GetCheckedState() != ECheckBoxState::Checked; })
									[
										SNew(STextBlock)
											.Text(FText::FromString(TEXT("...")))
									]
							]
						]
						+ SVerticalBox::Slot()
						.AutoHeight()
						[
							SNew(SHorizontalBox)
							+ SHorizontalBox::Slot()
							.AutoWidth()
							.HAlign(HAlign_Fill)
							[
								SNew(STextBlock)
								.MinDesiredWidth(120)
								.Text(LOCTEXT("SourceOutputFilenameLabelText", "Output Filename"))
								.ToolTipText(LOCTEXT("SourceOutputFilenameToolTip", "The name of the .cpp/.h files where this type will be stored. DO NOT include the file extension (.h, .cpp, etc)"))
							]
							+ SHorizontalBox::Slot()
							.HAlign(HAlign_Fill)
							.FillWidth(1.0f)
							[
								SAssignNew(FilenameBox, SEditableTextBox)
								.Text(this, &SSourcifyGraph::GetOutputFilenameForSelected)
								.HintText(LOCTEXT("SourceOutputFilenameHint", "DO NOT include the file extension (.h, .cpp, etc)"))
								.OnTextCommitted_Lambda([this](const FText& NewValue, ETextCommit::Type)
									{
										SetOutputFilenameForSelected(NewValue);
										ValidateConversionSettingsForSelected();
									}
								)
								.OnTextChanged_Lambda([this](const FText&)
									{
									}
								)
								.IsEnabled_Lambda([this]() -> bool { return ResourcifyCheckBox->GetCheckedState() != ECheckBoxState::Checked; })
							]
						]
						+ SVerticalBox::Slot()
						.AutoHeight()
						[
							SNew(SHorizontalBox)
							+ SHorizontalBox::Slot()
							.AutoWidth()
							.HAlign(HAlign_Fill)
							[
								SNew(STextBlock)
								.MinDesiredWidth(120)
								.Text(LOCTEXT("TypeNameLabelText", "Type Name"))
								.ToolTipText(LOCTEXT("TypeNameLabelToolTip", "The C++ name for this type (the proper UE type prefix will be included)"))
							]
							+ SHorizontalBox::Slot()
							.HAlign(HAlign_Fill)
							.FillWidth(1.0f)
							[
								SAssignNew(TypeNameBox, SEditableTextBox)
								.Text(this, &SSourcifyGraph::GetOutputTypeForSelected)
								.OnTextCommitted_Lambda([this](const FText& NewValue, ETextCommit::Type)
									{
										SetOutputTypeForSelected(NewValue, true);
										ValidateConversionSettingsForSelected();

										Sourcify::DependencyNode* DependencyNode = SelectedNodes[0]->GetInfo().Node.get();
										GraphObj->GetOnNodeTypeChangedDelegate().Broadcast(DependencyNode);
									})
								.OnTextChanged_Lambda([this](const FText& Text)
									{
										SetOutputTypeForSelected(Text);
									})
								.IsEnabled_Lambda([this]()->bool
									{
										if (SelectedNodes.Num() > 1)
										{
											return false;
										}
										return ResourcifyCheckBox->GetCheckedState() != ECheckBoxState::Checked;
									})
							]
						]
						+ SVerticalBox::Slot()
						.AutoHeight()
						.Padding(0.0f, 5.0f)
						[
							SNew(SWarningOrErrorBox)
								.MessageStyle(EMessageStyle::Error)
								.Visibility_Lambda([this] { return GetTypeNameErrorLabelVisibility(); })
								.Message_Lambda([this] { return GetTypeNameErrorLabelText(); })
						]
						+ SVerticalBox::Slot()
						.AutoHeight()
						[
							SNew(SHorizontalBox)
							+ SHorizontalBox::Slot()
							.AutoWidth()
							.HAlign(HAlign_Fill)
							[
								SNew(STextBlock)
								.MinDesiredWidth(120)
								.Text(LOCTEXT("PreviewTypeNameLabelText", "Preview Type Name"))
								.ToolTipText(LOCTEXT("PreviewTypeNameLabelToolTip", "The final output C++ name of this type with all pre/postfix rules applied."))
							]
							+ SHorizontalBox::Slot()
							.HAlign(HAlign_Fill)
							.FillWidth(1.0f)
							[
								SAssignNew(PreviewTypeNameBox, SEditableTextBox)
								.Text(this, &SSourcifyGraph::GetPreviewTypeName)
								.IsReadOnly(true)
							]
						]
					]
				]
				// "FATAL ERROR" Error label
				+ SOverlay::Slot()
				.HAlign(HAlign_Center)
				.VAlign(VAlign_Bottom)
				.Padding(30.0f)
				[
					SNew(SBorder)
					.BorderImage(FAppStyle::GetBrush("Brushes.Panel"))
					.BorderBackgroundColor(FLinearColor(0.5f, 0.5f, 0.5f, 0.75f))
					[
						SNew(SWarningOrErrorBox)
							.MessageStyle(EMessageStyle::Error)
							.Visibility_Lambda([this] { return GetSourcifyReadiness() == ReadyState::CanNeverBeReady ? EVisibility::Visible : EVisibility::Collapsed; })
							.Message_Lambda([this] { return FText::FromString(FatalErrorMessage); })
					]
				]
				// Server notifications overlay
				+ SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Bottom)
				.Padding(2.0f)
				[
					SNew(SBorder)
					.HAlign(HAlign_Right)
					.BorderImage(FAppStyle::GetBrush("Brushes.Panel"))
					.BorderBackgroundColor(FLinearColor(0.5f, 0.5f, 0.5f, 0.75f))
					[
						NewVersionAvailable
						? 
							StaticCastSharedRef<SWidget>(SNew(SButton)
							.ButtonColorAndOpacity(FAppStyle::GetColor("FlatButton.Success"))
							.ButtonStyle(FAppStyle::Get(), "FlatButton.Success")
							.OnClicked_Lambda([]()
								{
									FPlatformProcess::LaunchURL(TEXT("https://codemongous.com/download"), nullptr, nullptr);
									return FReply::Handled();
								}
							)
							.IsEnabled(NewVersionAvailable)
							[
								SNew(STextBlock)
									.Text(FText::FromString(TEXT("New Version Available (click here)")))
							])
						:
							StaticCastSharedRef<SWidget>(SNew(STextBlock)
							.Text(FText::FromString(TEXT("Latest Version Installed"))))
					]
				]
			]

			// Right side - Asset References Panel
			+SSplitter::Slot()
			.Value(0.25f)
			[
				SNew(SBorder)
				.BorderImage(FAppStyle::GetBrush("Brushes.Panel"))
				.Padding(4.0f)
				[
					SNew(SVerticalBox)
					+ SVerticalBox::Slot()
					.AutoHeight()
					.Padding(4.0f)
					[
						SNew(STextBlock)
						.Text(LOCTEXT("AssetReferencersTitle", "Asset Referencers"))
						.Font(FCoreStyle::GetDefaultFontStyle("Bold", 12))
					]
					+ SVerticalBox::Slot()
					.AutoHeight()
					.Padding(2.0f)
					[
						SNew(STextBlock)
						.Text(LOCTEXT("AssetReferencersDescription", "The assets below contain references to the types in the graph,\nand will need to be modified to the new, Sourcified types."))
						.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
					]
					+ SVerticalBox::Slot()
					.AutoHeight()
					.Padding(4.0f, 2.0f)
					[
						SNew(SHorizontalBox)
						+ SHorizontalBox::Slot()
						.AutoWidth()
						.VAlign(VAlign_Center)
						[
							SAssignNew(ShowGraphReferencersCheckBox, SCheckBox)
							.IsChecked(bShowGraphReferencers ? ECheckBoxState::Checked : ECheckBoxState::Unchecked)
							.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState)
								{
									bShowGraphReferencers = (NewState == ECheckBoxState::Checked);
									PopulateReferencerLists();
								})
						]
						+ SHorizontalBox::Slot()
						.AutoWidth()
						.VAlign(VAlign_Center)
						.Padding(4.0f, 0.0f, 0.0f, 0.0f)
						[
							SNew(STextBlock)
							.Text(LOCTEXT("ShowGraphReferencersLabel", "Include graph assets"))
							.ToolTipText(LOCTEXT("ShowGraphReferencersTooltip", "If checked, shows all referencers including those that appear in the main dependency graph. If unchecked, filters out referencers that are already shown in the graph."))
						]
					]
					// Loading indicator overlay
					+ SVerticalBox::Slot()
					.AutoHeight()
					.Padding(4.0f, 2.0f)
					[
						SNew(SBorder)
						.BorderImage(FAppStyle::GetBrush("Brushes.Panel"))
						.Padding(8.0f)
						.Visibility_Lambda([this]() { return bIsLoadingSourceControlStatus ? EVisibility::Visible : EVisibility::Collapsed; })
						[
							SNew(SVerticalBox)
							+ SVerticalBox::Slot()
							.AutoHeight()
							.Padding(0.0f, 0.0f, 0.0f, 4.0f)
							[
								SNew(SHorizontalBox)
								+ SHorizontalBox::Slot()
								.AutoWidth()
								.VAlign(VAlign_Center)
								.Padding(0.0f, 0.0f, 8.0f, 0.0f)
								[
									SAssignNew(ReferencersThrobber, SThrobber)
									.PieceImage(FAppStyle::GetBrush("Throbber.Chunk"))
								]
								+ SHorizontalBox::Slot()
								.FillWidth(1.0f)
								.VAlign(VAlign_Center)
								[
									SNew(STextBlock)
									.Text_Lambda([this]()
										{
											return FText::Format(
												LOCTEXT("LoadingSourceControlStatus", "Loading source control status... ({0}/{1})"),
												FText::AsNumber(SourceControlStatusLoadedCount),
												FText::AsNumber(SourceControlStatusTotalCount));
										})
									.Font(FCoreStyle::GetDefaultFontStyle("Regular", 9))
								]
							]
							+ SVerticalBox::Slot()
							.AutoHeight()
							[
								SAssignNew(ReferencersProgressBar, SProgressBar)
								.Percent_Lambda([this]() { return GetSourceControlLoadingProgress(); })
							]
						]
					]
					+ SVerticalBox::Slot()
					.FillHeight(1.0f)
					[
						SNew(SScrollBox)
						+ SScrollBox::Slot()
						.HAlign(HAlign_Fill)
						.Padding(2)
						[
							SNew(SBorder)
							.BorderImage(FAppStyle::GetBrush("NoBorder"))
							.Visibility_Lambda([this]() { return ReferencersList.Num() > 0 ? EVisibility::Visible : EVisibility::Collapsed; })
							[
								SAssignNew(ReferencersListView, SListView<TSharedPtr<FAssetReferencer>>)
								.ListItemsSource(&ReferencersList)
								.OnGenerateRow(this, &SSourcifyGraph::GenerateReferencerRow)
								.SelectionMode(ESelectionMode::None)
							]
						]
						+ SScrollBox::Slot()
						.HAlign(HAlign_Center)
						.Padding(10)
						[
							SNew(STextBlock)
							.Text(LOCTEXT("NoReferencersText", "No asset referencers found"))
							.ColorAndOpacity(FSlateColor::UseSubduedForeground())
							.Visibility_Lambda([this]()
								{
									return (ReferencersList.Num() == 0 && !bIsLoadingSourceControlStatus) ? EVisibility::Visible : EVisibility::Collapsed;
								})
						]
					]
				]
			]
		]
	];
	OpenButton->SetEnabled(InArgs._IsEnabled);
	SetCanTick(true);
}

void SSourcifyGraph::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
	if (bNeedsGraphRebuild && (InCurrentTime > GraphRebuildSliderDelay))
	{
		bNeedsGraphRebuild = false;
		RebuildGraph();
	}
}

FReply SSourcifyGraph::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
	return ReferenceViewerActions->ProcessCommandBindings(InKeyEvent) ? FReply::Handled() : FReply::Unhandled();
}

void SSourcifyGraph::FromDependencies(const Sourcify::BlueprintDependencies& Deps)
{
	GraphObj->SetGraphRoot(Deps.DependencyGraph);

	GatherUpAssetReferencers(Deps);
	
	// Populate the referencer lists for the UI panel
	PopulateReferencerLists();

	RebuildGraph();

	// Zoom once this frame to make sure widgets are visible, then zoom again so size is correct
	TriggerZoomToFit(0, 0);
	RegisterActiveTimer(0.5f, FWidgetActiveTimerDelegate::CreateSP(this, &SSourcifyGraph::TriggerZoomToFit));

	TemporaryPathBeingEdited = Deps.DependencyGraph->IsValid() ? FText() : FText(LOCTEXT("NoAssetsFound", "No Assets Found"));

	// Determine if any of our BPs are children of any other BPs
	// If so, create the error message and flag that an inheritance exists
	TMap<FString, TArray<FString>> BPInheritances;
	for (ISCW_UBlueprintConstPtr WrappedBP : Deps.BPs)
	{
		UBlueprint* BP = FSourcifyCoreUEWrapper::Unwrap(WrappedBP);
		if (UBlueprintGeneratedClass* ParentBP = Cast<UBlueprintGeneratedClass>(BP->ParentClass))
		{
			BPInheritances.FindOrAdd(ParentBP->GetName()).Add(BP->GetName());
		}
	}

	/*
	 *	BP Inheritance = Sourcification not allowed
		Warn the user of a Blueprint class inheritance (because it means functionality will be lost on Sourcification)
		Run through children and find the "SIMULATING" overlay text and replace it with our warning
	 */
	BPInheritanceExists = false;
	if (BPInheritances.Num())
	{
		FatalErrorMessage = TEXT("Sourcification not allowed. Blueprint class inheritance(s) detected:\n\n");
		for (const auto& BPInheritance : BPInheritances)
		{
			FString ParentName = BPInheritance.Key;
			// TODO - Find a cleaner way to get a friendly display name (may not be possible)
			if (ParentName.EndsWith(TEXT("_C")))
			{
				ParentName.LeftChopInline(2);
			}
			for (const FString& ChildName : BPInheritance.Value)
			{
				FatalErrorMessage.Append(ParentName + TEXT(" is the parent of ") + ChildName + TEXT("\n"));
			}
		}
		FatalErrorMessage.Append(TEXT("\nOnly Blueprints with a native parent class can be sourcified to ensure no functionality is lost during Sourcification."));
		BPInheritanceExists = true;
	}

	auto FindSimulationTextBlock = [this](auto&& Self, FChildren* Children) {

		STextBlock* TextBlock = nullptr;

		if (Children)
		{
			for (int32 i = 0; i < Children->Num(); i++)
			{
				TSharedRef<SWidget> Widget = Children->GetChildAt(i);
				if (Widget->GetTypeAsString() == TEXT("STextBlock"))
				{
					TextBlock = static_cast<STextBlock*>(&Widget.Get());
					if (TextBlock->GetText().ToString() == TEXT("SIMULATING"))
					{
						return TextBlock;
					}
				}
				TextBlock = Self(Self, Widget->GetAllChildren());
			}

			
		}
		return TextBlock;
		};

	FSourcifyModule& SourcifyModule = FModuleManager::LoadModuleChecked<FSourcifyModule>("Sourcify");
	
	if (BPInheritanceExists)
	{
		if (STextBlock* ErrorTextBlock = FindSimulationTextBlock(FindSimulationTextBlock, GraphEditorPtr->GetAllChildren()))
		{
			ErrorTextBlock->SetText(FText::FromString(TEXT("ERROR: BP INHERITANCE")));
			ErrorTextBlock->SetVisibility(EVisibility::Visible);	
		}
	}
	else if (SourcifyModule.GetLicenseStatus() == LicenseStatus::Error && !SourcifyModule.IsTrialSourcificationAllowed())
	{
		if (STextBlock* ErrorTextBlock = FindSimulationTextBlock(FindSimulationTextBlock, GraphEditorPtr->GetAllChildren()))
		{
			ErrorTextBlock->SetText(FText::FromString(TEXT("LICENSE REQUIRED")));
			ErrorTextBlock->SetVisibility(EVisibility::Visible);

			FatalErrorMessage = TEXT("A trial license allows sourcification of Enums and Structs with up to 1 level of dependencies. Blueprints and Structs with more than 1 level of dependencies require a valid license in order to Sourcify\nGo to Project Settings->Sourcify to activate your license");
		}
	}
}

EActiveTimerReturnType SSourcifyGraph::TriggerZoomToFit(double InCurrentTime, float InDeltaTime)
{
	if (GraphEditorPtr.IsValid())
	{
		GraphEditorPtr->ZoomToFit(false);
	}
	return EActiveTimerReturnType::Stop;
}

void SSourcifyGraph::SetCurrentRegistrySource(const FAssetManagerEditorRegistrySource* RegistrySource)
{
	RebuildGraph();
}

void SSourcifyGraph::ClearSelectedNodes()
{
	SelectedNodes.Empty();
}

void SSourcifyGraph::OnSelectedNodesChanged(const TSet<UObject*>& InSelectedNodes)
{
	bLastInputValidityCheckSuccessful = true;
	SelectedNodes.Empty(InSelectedNodes.Num());
	for (UObject* NodeObject : InSelectedNodes)
	{
		SelectedNodes.Add(Cast<UEdGraphNode_Sourcify>(NodeObject));
	}

	if (SelectedNodes.Num() == 1)
	{
		ValidateConversionSettingsForSelected();
	}
}

FActionMenuContent SSourcifyGraph::OnCreateActionMenu(UEdGraph* Graph, const FVector2D& NodeAddPosition, const TArray<UEdGraphPin*>& DraggedFromPins, bool AutoExpandActionMenu, SGraphEditor::FActionMenuClosed CloseActionMenuCallback) const
{
	return FActionMenuContent{};
}

FText SSourcifyGraph::GetOutputDirectoryForSelected() const
{
	if (SelectedNodes.Num())
	{
		for (auto Node : SelectedNodes)
		{
			if (Node->GetInfo().Node->ConversionSettings.OutputDirectory != SelectedNodes[0]->GetInfo().Node->ConversionSettings.OutputDirectory)
			{
				return SSourcifyGraphInternal::MultipleValuesSelected;
			}
		}
		return FText::FromString(SelectedNodes[0]->GetInfo().Node->ConversionSettings.OutputDirectory.c_str());
	}

	return SSourcifyGraphInternal::NothingSelected;
}

FText SSourcifyGraph::GetOutputFilenameForSelected() const
{
	if (SelectedNodes.Num())
	{
		for (auto Node : SelectedNodes)
		{
			if (Node->GetInfo().Node->ConversionSettings.OutputFileName != SelectedNodes[0]->GetInfo().Node->ConversionSettings.OutputFileName)
			{
				return SSourcifyGraphInternal::MultipleValuesSelected;
			}
		}
		return FText::FromString(SelectedNodes[0]->GetInfo().Node->ConversionSettings.OutputFileName.c_str());
	}

	return SSourcifyGraphInternal::NothingSelected;
}

FText SSourcifyGraph::GetOutputTypeForSelected() const
{
	if (SelectedNodes.Num())
	{
		for (auto Node : SelectedNodes)
		{
			if (Node->GetInfo().Node->ConversionSettings.TypeName != SelectedNodes[0]->GetInfo().Node->ConversionSettings.TypeName)
			{
				return SSourcifyGraphInternal::MultipleValuesSelected;
			}
		}
		return FText::FromString(SelectedNodes[0]->GetInfo().Node->ConversionSettings.TypeName.c_str());
	}

	return SSourcifyGraphInternal::NothingSelected;
}

FText SSourcifyGraph::GetPreviewTypeName() const
{
	// TODO handle spaces
	if (SelectedNodes.Num() > 1)
	{
		return SSourcifyGraphInternal::MultipleValuesSelected;
	}
	else if (SelectedNodes.Num())
	{
		Sourcify::DependencyNode* DependencyNode = SelectedNodes[0]->GetInfo().Node.get();
		
		return FText::FromString((DependencyNode->ConversionSettings.TypePrefix + DependencyNode->ConversionSettings.TypeName).c_str());
	}
	return SSourcifyGraphInternal::NothingSelected;
}

void SSourcifyGraph::SetOutputDirectoryForSelected(const FText& InDirectory)
{
	if (InDirectory.EqualTo(SSourcifyGraphInternal::MultipleValuesSelected))
	{
		return;
	}

	for (auto Node : SelectedNodes)
	{
		Node->GetInfo().Node->ConversionSettings.OutputDirectory = *InDirectory.ToString();
		Node->GetInfo().Node->UpdateReadyState();
		Node->SourcifyWidgetNode->UpdateErrorInfo();
	}
}

void SSourcifyGraph::SetOutputFilenameForSelected(const FText& InFilename)
{
	if (InFilename.EqualTo(SSourcifyGraphInternal::MultipleValuesSelected))
	{
		return;
	}
	
	for (auto Node : SelectedNodes)
	{
		Node->GetInfo().Node->ConversionSettings.OutputFileName = *InFilename.ToString();
		Node->GetInfo().Node->UpdateReadyState();
		Node->SourcifyWidgetNode->UpdateErrorInfo();
	}
}

void SSourcifyGraph::SetOutputTypeForSelected(const FText& InType, bool CheckIsReadyForOutput/* = false*/)
{
	for (auto Node : SelectedNodes)
	{
		Node->GetInfo().Node->ConversionSettings.TypeName = *InType.ToString();
		if (CheckIsReadyForOutput)
		{
			Node->GetInfo().Node->UpdateReadyState();
		}
		Node->SourcifyWidgetNode->UpdateErrorInfo();
	}
}

void SSourcifyGraph::RebuildGraph()
{
	// All assets are already discovered, build the graph now, if we have one
	if (GraphObj)
	{
		ClearSelectedNodes();
		GraphObj->RebuildGraph();
	}
}

void SSourcifyGraph::RefreshClicked()
{
	RebuildGraph();
	TriggerZoomToFit(0, 0);
	RegisterActiveTimer(0.1f, FWidgetActiveTimerDelegate::CreateSP(this, &SSourcifyGraph::TriggerZoomToFit));
}

FText SSourcifyGraph::GetAddressBarText() const
{
	if ( GraphObj )
	{
		if (SelectedNodes.Num() == 1)
		{
			return FText::FromString(SelectedNodes[0]->GetInfo().Node->AssetIdentifier.c_str());
		}
		else if (SelectedNodes.Num() > 1)
		{
			return LOCTEXT("SourcifyAddressBarText", "<multiple selected>");
		}
	}

	return FText();
}

void SSourcifyGraph::OnSourcifyingComplete(Sourcify::Results Results) const
{
	RequestClose();
}

void SSourcifyGraph::RequestClose() const
{
	ParentTab->RequestCloseTab();
}

void SSourcifyGraph::RegisterActions()
{
	ReferenceViewerActions = MakeShareable(new FUICommandList);
	FSourcifyEditorCommands::Register();

	ReferenceViewerActions->MapAction(
		FSourcifyEditorCommands::Get().ZoomToFit,
		FExecuteAction::CreateSP(this, &SSourcifyGraph::ZoomToFit),
		FCanExecuteAction::CreateSP(this, &SSourcifyGraph::CanZoomToFit));

	ReferenceViewerActions->MapAction(
		FGlobalEditorCommonCommands::Get().FindInContentBrowser,
		FExecuteAction::CreateSP(this, &SSourcifyGraph::ShowSelectionInContentBrowser),
		FCanExecuteAction::CreateSP(this, &SSourcifyGraph::HasAtLeastOnePackageNodeSelected));

	ReferenceViewerActions->MapAction(
		FSourcifyEditorCommands::Get().OpenSelectedInAssetEditor,
		FExecuteAction::CreateSP(this, &SSourcifyGraph::OpenSelectedInAssetEditor),
		FCanExecuteAction::CreateSP(this, &SSourcifyGraph::HasAtLeastOneRealNodeSelected));
	
	ReferenceViewerActions->MapAction(
		FSourcifyEditorCommands::Get().ShowReferenceTree,
		FExecuteAction::CreateSP(this, &SSourcifyGraph::ShowReferenceTree),
		FCanExecuteAction::CreateSP(this, &SSourcifyGraph::HasExactlyOnePackageNodeSelected));
}

void SSourcifyGraph::ShowSelectionInContentBrowser()
{
	TArray<FAssetData> AssetList;

	// Build up a list of selected assets from the graph selection set
	TSet<UObject*> Nodes = GraphEditorPtr->GetSelectedNodes();
	for (FGraphPanelSelectionSet::TConstIterator It(Nodes); It; ++It)
	{
		if (UEdGraphNode_Sourcify* ReferenceNode = Cast<UEdGraphNode_Sourcify>(*It))
		{
			if (ReferenceNode->GetAssetData().IsValid())
			{
				AssetList.Add(ReferenceNode->GetAssetData());
			}
		}
	}

	if (AssetList.Num() > 0)
	{
		GEditor->SyncBrowserToObjects(AssetList);
	}
}

void SSourcifyGraph::OpenSelectedInAssetEditor()
{
	TArray<FAssetIdentifier> IdentifiersToEdit;
	TSet<UObject*> Nodes = GraphEditorPtr->GetSelectedNodes();
	for (FGraphPanelSelectionSet::TConstIterator It(Nodes); It; ++It)
	{
		if (UEdGraphNode_Sourcify* ReferenceNode = Cast<UEdGraphNode_Sourcify>(*It))
		{
			if (!ReferenceNode->IsCollapsed())
			{
				ReferenceNode->GetAllIdentifiers(IdentifiersToEdit);
			}
		}
	}

	// This will handle packages as well as searchable names if other systems register
	FEditorDelegates::OnEditAssetIdentifiers.Broadcast(IdentifiersToEdit);
}

void SSourcifyGraph::ShowReferenceTree() const
{
	UObject* SelectedObject = GetObjectFromSingleSelectedNode();

	if ( SelectedObject )
	{
		bool bObjectWasSelected = false;
		for (FSelectionIterator It(*GEditor->GetSelectedObjects()) ; It; ++It)
		{
			if ( (*It) == SelectedObject )
			{
				GEditor->GetSelectedObjects()->Deselect( SelectedObject );
				bObjectWasSelected = true;
			}
		}

		ObjectTools::ShowReferenceGraph( SelectedObject );

		if ( bObjectWasSelected )
		{
			GEditor->GetSelectedObjects()->Select( SelectedObject );
		}
	}
}

void SSourcifyGraph::ReCenterGraphOnNodes(const TSet<UObject*>& Nodes) const
{
	TArray<FAssetIdentifier> NewGraphRootNames;
	FIntPoint TotalNodePos(ForceInitToZero);
	for ( auto NodeIt = Nodes.CreateConstIterator(); NodeIt; ++NodeIt )
	{
		UEdGraphNode_Sourcify* ReferenceNode = Cast<UEdGraphNode_Sourcify>(*NodeIt);
		if ( ReferenceNode )
		{
			ReferenceNode->GetAllIdentifiers(NewGraphRootNames);
			TotalNodePos.X += ReferenceNode->NodePosX;
			TotalNodePos.Y += ReferenceNode->NodePosY;
		}
	}

	if ( NewGraphRootNames.Num() > 0 )
	{
		const FIntPoint AverageNodePos = TotalNodePos / NewGraphRootNames.Num();
		//GraphObj->SetGraphRoot(NewGraphRootNames, AverageNodePos);
		UEdGraphNode_Sourcify* NewRootNode = GraphObj->RebuildGraph();

		if ( NewRootNode && ensure(GraphEditorPtr.IsValid()) )
		{
			GraphEditorPtr->ClearSelectionSet();
			GraphEditorPtr->SetNodeSelection(NewRootNode, true);
		}

		// Set the initial history data
		//HistoryManager.AddHistoryData();
	}
}

UObject* SSourcifyGraph::GetObjectFromSingleSelectedNode() const
{
	UObject* ReturnObject = nullptr;

	TSet<UObject*> Nodes = GraphEditorPtr->GetSelectedNodes();
	if ( ensure(Nodes.Num()) == 1 )
	{
		UEdGraphNode_Sourcify* ReferenceNode = Cast<UEdGraphNode_Sourcify>(Nodes.Array()[0]);
		if ( ReferenceNode )
		{
			const FAssetData& AssetData = ReferenceNode->GetAssetData();
			if (AssetData.IsAssetLoaded())
			{
				ReturnObject = AssetData.GetAsset();
			}
			else
			{
				FScopedSlowTask SlowTask(0, LOCTEXT("LoadingSelectedObject", "Loading selection..."));
				SlowTask.MakeDialog();
				ReturnObject = AssetData.GetAsset();
			}
		}
	}

	return ReturnObject;
}

void SSourcifyGraph::GetPackageNamesFromSelectedNodes(TSet<FName>& OutNames) const
{
	TSet<UObject*> Nodes = GraphEditorPtr->GetSelectedNodes();
	for (UObject* Node : Nodes)
	{
		UEdGraphNode_Sourcify* ReferenceNode = Cast<UEdGraphNode_Sourcify>(Node);
		if (ReferenceNode)
		{
			TArray<FName> NodePackageNames;
			ReferenceNode->GetAllPackageNames(NodePackageNames);
			OutNames.Append(NodePackageNames);
		}
	}
}

bool SSourcifyGraph::HasExactlyOneNodeSelected() const
{
	if ( GraphEditorPtr.IsValid() )
	{
		return GraphEditorPtr->GetSelectedNodes().Num() == 1;
	}
	
	return false;
}

bool SSourcifyGraph::IsAnyNodeSelected() const
{
	return SelectedNodes.Num() > 0;
}

bool SSourcifyGraph::HasExactlyOnePackageNodeSelected() const
{
	if (GraphEditorPtr.IsValid())
	{
		if (GraphEditorPtr->GetSelectedNodes().Num() != 1)
		{
			return false;
		}

		TSet<UObject*> Nodes = GraphEditorPtr->GetSelectedNodes();
		for (UObject* Node : Nodes)
		{
			UEdGraphNode_Sourcify* SourcifyNode = Cast<UEdGraphNode_Sourcify>(Node);
			if (SourcifyNode)
			{
				if (SourcifyNode->IsPackage())
				{
					return true;
				}
			}
			return false;
		}
	}

	return false;
}

bool SSourcifyGraph::HasAtLeastOnePackageNodeSelected() const
{
	if (GraphEditorPtr.IsValid())
	{
		TSet<UObject*> Nodes = GraphEditorPtr->GetSelectedNodes();
		for (UObject* Node : Nodes)
		{
			UEdGraphNode_Sourcify* SourcifyNode = Cast<UEdGraphNode_Sourcify>(Node);
			if (SourcifyNode)
			{
				if (SourcifyNode->IsPackage())
				{
					return true;
				}
			}
		}
	}

	return false;
}

bool SSourcifyGraph::HasAtLeastOneRealNodeSelected() const
{
	if (GraphEditorPtr.IsValid())
	{
		TSet<UObject*> Nodes = GraphEditorPtr->GetSelectedNodes();
		for (UObject* Node : Nodes)
		{
			UEdGraphNode_Sourcify* SourcifyNode = Cast<UEdGraphNode_Sourcify>(Node);
			if (SourcifyNode)
			{
				if (!SourcifyNode->IsCollapsed())
				{
					return true;
				}
			}
		}
	}

	return false;
}

void SSourcifyGraph::OnInitialAssetRegistrySearchComplete()
{
	if ( GraphObj )
	{
		GraphObj->RebuildGraph();
	}
}

void SSourcifyGraph::ZoomToFit()
{
	if (GraphEditorPtr.IsValid())
	{
		GraphEditorPtr->ZoomToFit(true);
	}
}

bool SSourcifyGraph::CanZoomToFit() const
{
	if (GraphEditorPtr.IsValid())
	{
		return true;
	}

	return false;
}

TSharedRef<SWidget> SSourcifyGraph::MakeToolBar()
{

	FToolBarBuilder ToolBarBuilder(ReferenceViewerActions, FMultiBoxCustomization::None, TSharedPtr<FExtender>(), true);
	ToolBarBuilder.SetStyle(&FSourcifyGraphStyle::Get(), "AssetEditorToolbar");
	ToolBarBuilder.BeginSection("Test");

	ToolBarBuilder.AddToolBarButton(
		FUIAction(FExecuteAction::CreateSP(this, &SSourcifyGraph::RefreshClicked)),
		NAME_None,
		TAttribute<FText>(),
		TAttribute<FText>(),
		FSlateIcon(FSourcifyGraphStyle::Get().GetStyleSetName(), "Icons.Refresh"));

	ToolBarBuilder.EndSection();

	return ToolBarBuilder.MakeWidget();
}

void SSourcifyGraph::GatherUpAssetReferencers(const Sourcify::BlueprintDependencies& Deps)
{
	IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
	AssetRegistry.SearchAllAssets(true);

	MainGraphAssetPaths.Empty();
	Referencers.Empty();
	GraphReferencers.Empty();
	ReferencerToNodeMap.Empty();
	NodeToReferencerMap.Empty();
	TArray<FName> TempReferencers;
	
	// Track visited packages to avoid infinite loops during recursive gathering
	TSet<FName> VisitedPackages;

	// Helper lambda to recursively gather referencers, expanding through UserDefinedStructs and Redirectors
	// This matches the logic in ReferenceUpdater::GatherReferencersRecursive
	auto GatherReferencersRecursive = [&AssetRegistry, &VisitedPackages](FName PackagePath, TArray<FName>& OutReferencers, TArray<FName>& OutUserDefinedStructReferencers, TArray<FName>& OutRedirectorReferencers)
	{
		if (VisitedPackages.Contains(PackagePath))
		{
			return;
		}
		VisitedPackages.Add(PackagePath);
		
		TArray<FAssetDependency> AssetReferencers;
		AssetRegistry.GetReferencers(PackagePath, AssetReferencers);

		for (const FAssetDependency& ReferencerAssetData : AssetReferencers)
		{
			FName PackageName = ReferencerAssetData.AssetId.PackageName;
			
			TArray<FAssetData> ReferencerAssets;
			AssetRegistry.GetAssetsByPackageName(PackageName, ReferencerAssets, true);
			
			bool bIsRedirector = false;
			bool bIsUserDefinedStruct = false;
			for (const FAssetData& RefAssetData : ReferencerAssets)
			{
				FName ClassName = RefAssetData.AssetClassPath.GetAssetName();
				// Check if this is an ObjectRedirector - we need to follow it
				if (ClassName == FName("ObjectRedirector"))
				{
					bIsRedirector = true;
				}
				// Check if this is a UserDefinedStruct for recursive expansion
				if (ClassName == FName("UserDefinedStruct"))
				{
					bIsUserDefinedStruct = true;
				}
			}
			
			// Follow redirectors by recursively gathering their referencers
			if (bIsRedirector)
			{
				OutRedirectorReferencers.AddUnique(PackageName);
				continue;
			}
			
			OutReferencers.AddUnique(PackageName);
			
			// If this referencer is a UserDefinedStruct, we need to recursively gather its referencers too.
			// This ensures we detect assets that use the struct in default values.
			if (bIsUserDefinedStruct)
			{
				OutUserDefinedStructReferencers.AddUnique(PackageName);
			}
		}
	};

	auto ProcessAssets = [this, &AssetRegistry, &TempReferencers, &GatherReferencersRecursive, &VisitedPackages](const auto& WrappedAssets)
	{
		for (const ISCW_UObjectConstPtr WrappedAsset : WrappedAssets)
		{
			UObject* Asset = FSourcifyCoreUEWrapper::Unwrap(WrappedAsset);
			FName AssetPath(*Asset->GetOuter()->GetPathName());
			MainGraphAssetPaths.Add(AssetPath);
			
			// Gather referencers recursively, expanding through UserDefinedStructs and Redirectors
			TArray<FName> DirectReferencers;
			TArray<FName> UserDefinedStructReferencers;
			TArray<FName> RedirectorReferencers;
			GatherReferencersRecursive(AssetPath, DirectReferencers, UserDefinedStructReferencers, RedirectorReferencers);
			
			// Helper to process a path and gather its referencers
			auto ProcessPath = [&GatherReferencersRecursive, &DirectReferencers, &UserDefinedStructReferencers, &RedirectorReferencers](FName Path)
			{
				TArray<FName> NewReferencers;
				TArray<FName> NestedStructReferencers;
				TArray<FName> NestedRedirectorReferencers;
				GatherReferencersRecursive(Path, NewReferencers, NestedStructReferencers, NestedRedirectorReferencers);
				
				for (const FName& Referencer : NewReferencers)
				{
					DirectReferencers.AddUnique(Referencer);
				}
				for (const FName& NestedStruct : NestedStructReferencers)
				{
					UserDefinedStructReferencers.AddUnique(NestedStruct);
				}
				for (const FName& NestedRedirector : NestedRedirectorReferencers)
				{
					RedirectorReferencers.AddUnique(NestedRedirector);
				}
			};
			
			// Process both Redirectors and UserDefinedStructs until no new items are found
			int32 RedirectorIndex = 0;
			int32 StructIndex = 0;
			while (RedirectorIndex < RedirectorReferencers.Num() || StructIndex < UserDefinedStructReferencers.Num())
			{
				while (RedirectorIndex < RedirectorReferencers.Num())
				{
					ProcessPath(RedirectorReferencers[RedirectorIndex++]);
				}
				while (StructIndex < UserDefinedStructReferencers.Num())
				{
					ProcessPath(UserDefinedStructReferencers[StructIndex++]);
				}
			}
			
			// Build the bidirectional maps and add to temp referencers
			for (const FName& PackageName : DirectReferencers)
			{
				TempReferencers.AddUnique(PackageName);
				ReferencerToNodeMap.FindOrAdd(PackageName).AddUnique(AssetPath);
				NodeToReferencerMap.FindOrAdd(AssetPath).AddUnique(PackageName);
			}
		}
	};

	ProcessAssets(Deps.BPs);
	ProcessAssets(Deps.Structs);
	ProcessAssets(Deps.Enums);

	// Separate referencers into those in the graph and those external to the graph
	for (const FName& ReferencerName : TempReferencers)
	{
		if (MainGraphAssetPaths.Contains(ReferencerName))
		{
			GraphReferencers.AddUnique(ReferencerName);
		}
		else
		{
			Referencers.AddUnique(ReferencerName);
		}
	}
}

bool SSourcifyGraph::OpenPlatformDirectoryPicker(FString& OutDirectory, FString DefaultPath)
{
	bool bFolderSelected = false;
	IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
	if (DesktopPlatform)
	{
		void* TopWindowHandle = FSlateApplication::Get().GetActiveTopLevelWindow().IsValid() ? FSlateApplication::Get().GetActiveTopLevelWindow()->GetNativeWindow()->GetOSWindowHandle() : nullptr;

		FString FolderName = DefaultPath;
		bFolderSelected = DesktopPlatform->OpenDirectoryDialog(
			TopWindowHandle,
			TEXT(""),
			DefaultPath,
			FolderName
		);

		if (bFolderSelected)
		{
			OutDirectory = FolderName;
			SelectedNodes[0]->GetInfo().Node->ConversionSettings.OutputDirectory = *OutDirectory;
		}
	}

	return bFolderSelected;
}

FReply SSourcifyGraph::BrowseForDirectory()
{
	// Guarantee OutputDirectory is set to something valid for when the user cancels the directory picker
	if (OutputDirectory.IsEmpty())
	{
		if (!SelectedNodes.IsEmpty())
		{
			OutputDirectory = SelectedNodes[0]->GetInfo().Node->ConversionSettings.OutputDirectory.c_str();
		}
	}

	if (OutputDirectory.IsEmpty())
	{
		OutputDirectory = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir());
	}

	OpenPlatformDirectoryPicker(OutputDirectory, OutputDirectory);
	SetOutputDirectoryForSelected(FText::FromString(OutputDirectory));
	ValidateConversionSettingsForSelected();
	return FReply::Handled();
}

FReply SSourcifyGraph::OnSourcifyClicked()
{	
	FSourcifyModule& SourcifyModule = FSourcifyModule::Get();

	if (const Sourcify::BlueprintDependencies* Dependencies = SourcifyModule.GetPreSourcifyDependencies())
	{
		vector<ISCW_UPackagePtr> PackagesToCheckoutFromGraph = Dependencies->GetPackagesToCheckout();
		if (!PackagesToCheckoutFromGraph.empty())
		{
			TArray<UPackage*> PackagesToCheckout;
			for (ISCW_UPackageConstPtr WrappedPackage : PackagesToCheckoutFromGraph)
			{
				PackagesToCheckout.Add(FSourcifyCoreUEWrapper::Unwrap(WrappedPackage));
			}

			TArray<UPackage*> PackagesCheckedOut;
			TArray<UPackage*> PackagesNotNeedingCheckout;
			if (!FEditorFileUtils::PromptToCheckoutPackages(false, PackagesToCheckout, &PackagesCheckedOut, &PackagesNotNeedingCheckout, false, false) ||
				(PackagesCheckedOut.Num() + PackagesNotNeedingCheckout.Num()) != PackagesToCheckout.Num())
			{
				FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SourcifyCheckoutFailed", "Sourcification cannot continue until all required assets are checked out."));
				return FReply::Handled();
			}
		}
	}

	Sourcify::Results SourcifyResults = SourcifyModule.Sourcify();
	return FReply::Handled();
}

FReply SSourcifyGraph::OnSettingsClicked()
{
	if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
	{
		SettingsModule->ShowViewer("Project", "Plugins", "Sourcify");
	}
	return FReply::Handled();
}

EVisibility SSourcifyGraph::GetTypeNameErrorLabelVisibility()
{
	return GetTypeNameErrorLabelText().IsEmpty() ? EVisibility::Collapsed : EVisibility::Visible;
}

FText SSourcifyGraph::GetTypeNameErrorLabelText()
{
	if (!bLastInputValidityCheckSuccessful)
	{
		for (auto Node : SelectedNodes)
		{
			bLastInputValidityCheckSuccessful = Node->GetInfo().Node->IsReadyForOutput() != NodeErrorStatus::Fatal;
			if (!bLastInputValidityCheckSuccessful)
			{
				LastInputValidityErrorText = FText::FromString(Node->GetInfo().Node->ConversionSettings.LastInputValidityErrorText.c_str());
				break;
			}
		}

		return LastInputValidityErrorText;
	}

	return FText::GetEmpty();
}

void SSourcifyGraph::ValidateConversionSettingsForSelected()
{
	for (const UEdGraphNode_Sourcify* Node : SelectedNodes)
	{
		bLastInputValidityCheckSuccessful = Node->ValidateConversionSettings() != NodeErrorStatus::Fatal;
		if (!bLastInputValidityCheckSuccessful)
		{
			LastInputValidityErrorText = FText::FromString(Node->GetInfo().Node->ConversionSettings.LastInputValidityErrorText.c_str());
			break;
		}
	}
}

EVisibility SSourcifyGraph::CanResourcifySelectedNodes() const
{
	for (auto Node : SelectedNodes)
	{
		if (Node->GetInfo().Node->CanBeResourcified)
		{
			return EVisibility::Visible;
		}
	}

	return EVisibility::Collapsed;
}

// Called every tick by "Sourcify" button and potentially other widgets
SSourcifyGraph::ReadyState SSourcifyGraph::GetSourcifyReadiness() const
{
	if (BPInheritanceExists)
	{
		return SSourcifyGraph::ReadyState::CanNeverBeReady;
	}
	FSourcifyModule& SourcifyModule = FModuleManager::LoadModuleChecked<FSourcifyModule>("Sourcify");
	
	if (SourcifyModule.GetLicenseStatus() == LicenseStatus::Error && !SourcifyModule.IsTrialSourcificationAllowed())
	{
		return SSourcifyGraph::ReadyState::CanNeverBeReady;
	}

	if (!GraphObj->AreAllNodesReadyToBeSourcified())
	{
		return SSourcifyGraph::ReadyState::NotReady;
	}

	return SSourcifyGraph::ReadyState::Ready; 
}

void SSourcifyGraph::PopulateReferencerLists()
{
	ReferencersList.Empty();

	// Helper to add a referencer to the list
	auto AddReferencer = [this](const FName& ReferencerPath)
	{
		TSharedPtr<FAssetReferencer> Ref = MakeShared<FAssetReferencer>();
		Ref->AssetPath = ReferencerPath;
		
		// Find the first node in the main graph that this referencer references
		if (const TArray<FName>* ReferencedNodes = ReferencerToNodeMap.Find(ReferencerPath))
		{
			if (ReferencedNodes->Num() > 0)
			{
				Ref->ReferencedNodePath = (*ReferencedNodes)[0];
			}
		}
		
		ReferencersList.Add(Ref);
	};

	// Always add external referencers (not in graph)
	for (const FName& ReferencerPath : Referencers)
	{
		AddReferencer(ReferencerPath);
	}

	// Add graph referencers only if the checkbox is checked
	if (bShowGraphReferencers)
	{
		for (const FName& ReferencerPath : GraphReferencers)
		{
			AddReferencer(ReferencerPath);
		}
	}

	// Refresh list view if it exists
	if (ReferencersListView.IsValid())
	{
		ReferencersListView->RequestListRefresh();
	}

	// Start async loading of source control status
	StartAsyncSourceControlStatusLoading();
}

float SSourcifyGraph::GetSourceControlLoadingProgress() const
{
	if (SourceControlStatusTotalCount == 0)
	{
		return 0.0f;
	}
	return static_cast<float>(SourceControlStatusLoadedCount) / static_cast<float>(SourceControlStatusTotalCount);
}

void SSourcifyGraph::StartAsyncSourceControlStatusLoading()
{
	// Clear previous cache and reset counters
	SourceControlStatusCache.Empty();
	SourceControlStatusLoadedCount = 0;
	SourceControlStatusTotalCount = ReferencersList.Num();

	if (SourceControlStatusTotalCount == 0 || !ISourceControlModule::Get().IsEnabled())
	{
		bIsLoadingSourceControlStatus = false;
		return;
	}

	bIsLoadingSourceControlStatus = true;

	// Capture a weak pointer to this widget for the async callbacks
	TWeakPtr<SSourcifyGraph> WeakThisPtr = StaticCastSharedRef<SSourcifyGraph>(AsShared());

	// Collect all file paths for batch query
	TArray<FString> FilePaths;
	TMap<FString, FName> FilePathToAssetPath;
	TMap<FString, FAssetData> FilePathToAssetData;

	for (const TSharedPtr<FAssetReferencer>& Referencer : ReferencersList)
	{
		FName AssetPath = Referencer->AssetPath;
		FAssetData AssetData = GetAssetDataForPath(AssetPath);

		// Mark as loading in the cache
		FSourceControlStatusCache LoadingCache;
		LoadingCache.Status = ESourceControlCheckoutStatus::Loading;
		SourceControlStatusCache.Add(AssetPath, LoadingCache);

		// Convert asset path to file path
		FString PackagePath = AssetPath.ToString();
		FString FilePath;

		// Determine extension from AssetData class path instead of loading
		FString Extension = FPackageName::GetAssetPackageExtension();
		FName ClassName = AssetData.AssetClassPath.GetAssetName();
		if (ClassName == FName("World") || ClassName == FName("Level"))
		{
			Extension = FPackageName::GetMapPackageExtension();
		}

		if (FPackageName::TryConvertLongPackageNameToFilename(PackagePath, FilePath, Extension))
		{
			FilePaths.Add(FilePath);
			FilePathToAssetPath.Add(FilePath, AssetPath);
			FilePathToAssetData.Add(FilePath, AssetData);
		}
	}

	if (FilePaths.Num() == 0)
	{
		bIsLoadingSourceControlStatus = false;
		return;
	}

	// Use the source control provider's async API to update state for all files
	ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
	
	// Execute the update state operation asynchronously
	SourceControlProvider.Execute(
		ISourceControlOperation::Create<FUpdateStatus>(),
		FilePaths,
		EConcurrency::Asynchronous,
		FSourceControlOperationComplete::CreateLambda([WeakThisPtr, FilePathToAssetPath, FilePathToAssetData](const FSourceControlOperationRef& Operation, ECommandResult::Type Result)
		{
			TSharedPtr<SSourcifyGraph> StrongThis = WeakThisPtr.Pin();
			if (!StrongThis.IsValid())
			{
				return;
			}

			ISourceControlProvider& Provider = ISourceControlModule::Get().GetProvider();

			// Process results for each file
			for (const auto& Pair : FilePathToAssetPath)
			{
				const FString& FilePath = Pair.Key;
				const FName& AssetPath = Pair.Value;

				FSourceControlStatusCache StatusCache;
				StatusCache.Status = ESourceControlCheckoutStatus::NotUnderSourceControl;

				FSourceControlStatePtr SourceControlState = Provider.GetState(FilePath, EStateCacheUsage::Use);
				if (SourceControlState.IsValid())
				{
					FString OtherUser;
					if (SourceControlState->IsCheckedOutOther(&OtherUser))
					{
						StatusCache.Status = ESourceControlCheckoutStatus::CheckedOutByOther;
						StatusCache.OtherUserName = OtherUser;
					}
					else if (SourceControlState->IsCheckedOut())
					{
						StatusCache.Status = ESourceControlCheckoutStatus::CheckedOutByYou;
					}
					else if (SourceControlState->CanCheckout())
					{
						StatusCache.Status = ESourceControlCheckoutStatus::AvailableForCheckout;
					}
				}

				StrongThis->OnSourceControlStatusLoaded(AssetPath, StatusCache);
			}
		})
	);
}

void SSourcifyGraph::OnSourceControlStatusLoaded(FName AssetPath, FSourceControlStatusCache StatusCache)
{
	// Update the cache
	SourceControlStatusCache.Add(AssetPath, StatusCache);

	// Increment the counter
	SourceControlStatusLoadedCount++;

	// Check if all items are loaded
	if (SourceControlStatusLoadedCount >= SourceControlStatusTotalCount)
	{
		bIsLoadingSourceControlStatus = false;
	}

	// Refresh the list view to show the updated status
	if (ReferencersListView.IsValid())
	{
		ReferencersListView->RequestListRefresh();
	}
}

TSharedRef<ITableRow> SSourcifyGraph::GenerateReferencerRow(TSharedPtr<FAssetReferencer> Item, const TSharedRef<STableViewBase>& OwnerTable)
{
	// Get asset data without loading the asset
	FAssetData AssetData = GetAssetDataForPath(Item->AssetPath);

	if (!AssetData.IsValid())
	{
		return SNew(STableRow<TSharedPtr<FAssetReferencer>>, OwnerTable)
			[
				SNew(STextBlock)
				.Text(FText::FromName(Item->AssetPath))
			];
	}

	// Extract display name from FAssetData without loading
	FString DisplayName = AssetData.AssetName.ToString();
	
	// Check for ActorLabel tag (used by actors in levels) without loading
	FString ActorLabel;
	if (AssetData.GetTagValue(FName("ActorLabel"), ActorLabel))
	{
		FString OuterPathName = AssetData.GetOptionalOuterPathName().ToString();
		OuterPathName.LeftInline(OuterPathName.Find(TEXT("."))); // Remove the object name from the path
		DisplayName = ActorLabel;
		DisplayName.Appendf(TEXT(" (%s)"), *OuterPathName);
	}

	FText PathName = FText::FromString(AssetData.GetObjectPathString());

	FLinearColor AssetTypeColor;
	FAssetThumbnailConfig ThumbnailConfig;
	TSharedPtr<FAssetThumbnail> AssetThumbnail = MakeAssetThumbnail(AssetTypeColor, ThumbnailConfig, AssetData);

	// Capture Item for lambda usage
	FName ItemAssetPath = Item->AssetPath;
	TWeakPtr<SSourcifyGraph> LocalWeakThis = StaticCastSharedRef<SSourcifyGraph>(AsShared());

	// Create a custom row that tracks mouse hover
	class SReferencerTableRow : public STableRow<TSharedPtr<FAssetReferencer>>
	{
	public:
		TWeakPtr<SSourcifyGraph> WeakOwner;
		FName AssetPath;

		virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override
		{
			STableRow::OnMouseEnter(MyGeometry, MouseEvent);
			if (TSharedPtr<SSourcifyGraph> Owner = WeakOwner.Pin())
			{
				Owner->OnReferencerMouseEnter(AssetPath);
			}
		}

		virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override
		{
			STableRow::OnMouseLeave(MouseEvent);
			if (TSharedPtr<SSourcifyGraph> Owner = WeakOwner.Pin())
			{
				Owner->OnReferencerMouseLeave();
			}
		}
	};

	TSharedRef<SReferencerTableRow> TableRow = SNew(SReferencerTableRow, OwnerTable);
	TableRow->WeakOwner = LocalWeakThis;
	TableRow->AssetPath = ItemAssetPath;

	TableRow->SetContent(
		SNew(SBorder)
		.BorderImage_Lambda([LocalWeakThis, ItemAssetPath]()
			{
				if (TSharedPtr<SSourcifyGraph> StrongThis = LocalWeakThis.Pin())
				{
					return StrongThis->IsReferencerHighlighted(ItemAssetPath) ? FAppStyle::GetBrush("DetailsView.CategoryTop_Hovered") : FAppStyle::GetBrush("NoBorder");
				}
				return FAppStyle::GetBrush("NoBorder");
			})
		.BorderBackgroundColor_Lambda([LocalWeakThis, ItemAssetPath]()
			{
				if (TSharedPtr<SSourcifyGraph> StrongThis = LocalWeakThis.Pin())
				{
					return StrongThis->IsReferencerHighlighted(ItemAssetPath) ? FLinearColor(1.f, 1.f, 1.f, 0.5f) : FLinearColor::Transparent;
				}
				return FLinearColor::Transparent;
			})
		[
			SNew(SHorizontalBox)
			+ SHorizontalBox::Slot()
			.HAlign(HAlign_Left)
			.VAlign(VAlign_Top)
			.AutoWidth()
			.Padding(SSourcifyGraphInternal::GetRowThumbnailPadding())
			[
				SNew(SBorder)
				.BorderImage(FAppStyle::GetBrush("WhiteBrush"))
				.Padding(2)
				.BorderBackgroundColor(AssetTypeColor)
				.OnMouseDoubleClick_Lambda([ItemAssetPath](const FGeometry& InGeometry, const FPointerEvent& InEvent)->FReply
					{
						// Load asset only when user explicitly double-clicks to open it
						FSoftObjectPath SoftObjectPath(ItemAssetPath.ToString());
						if (UObject* Asset = SoftObjectPath.TryLoad())
						{
							GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Asset);
						}
						return FReply::Handled();
					})
				[
					SNew(SBox)
					.WidthOverride(AssetThumbnail->GetSize().X)
					.HeightOverride(AssetThumbnail->GetSize().Y)
					[
						AssetThumbnail->MakeThumbnailWidget(ThumbnailConfig)
					]
				]
			]
			+ SHorizontalBox::Slot()
			.HAlign(HAlign_Fill)
			.VAlign(VAlign_Center)
			.AutoWidth()
			[
				SNew(SVerticalBox)
				+ SVerticalBox::Slot()
				.HAlign(HAlign_Left)
				.AutoHeight()
				[
					SNew(STextBlock)
					.Text(FText::FromString(DisplayName))
					.ColorAndOpacity(AssetTypeColor)
				]
				+ SVerticalBox::Slot()
				.HAlign(HAlign_Left)
				.AutoHeight()
				[
					SNew(SHorizontalBox)
					+ SHorizontalBox::Slot()
					.AutoWidth()
					[
						PropertyCustomizationHelpers::MakeBrowseButton(FSimpleDelegate::CreateLambda([ItemAssetPath]() -> void
							{
								// Sync to asset in content browser using asset data (no load needed)
								TArray<FAssetData> AssetsToSync;
								IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
								AssetRegistry.GetAssetsByPackageName(ItemAssetPath, AssetsToSync, true);
								if (!AssetsToSync.IsEmpty())
								{
									FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
									ContentBrowserModule.Get().SyncBrowserToAssets(AssetsToSync);
								}
							}), PathName)
					]
					+ SHorizontalBox::Slot()
					.AutoWidth()
					.Padding(4.0f, 0.0f, 0.0f, 0.0f)
					.VAlign(VAlign_Center)
					[
						SNew(STextBlock)
						.Text_Lambda([LocalWeakThis, ItemAssetPath, AssetData]()
							{
								if (TSharedPtr<SSourcifyGraph> StrongThis = LocalWeakThis.Pin())
								{
									return StrongThis->GetSourceControlCheckoutStatusText(ItemAssetPath, AssetData);
								}
								return FText::GetEmpty();
							})
						.Font(FCoreStyle::GetDefaultFontStyle("Italic", 8))
						.ColorAndOpacity_Lambda([LocalWeakThis, ItemAssetPath, AssetData]()
							{
								if (TSharedPtr<SSourcifyGraph> StrongThis = LocalWeakThis.Pin())
								{
									return StrongThis->GetSourceControlCheckoutStatusColor(ItemAssetPath, AssetData);
								}
								return FSlateColor::UseSubduedForeground();
							})
					]
				]
			]
		]
	);

	return TableRow;
}

TSharedPtr<FAssetThumbnail> SSourcifyGraph::MakeAssetThumbnail(FLinearColor& OutAssetColor, FAssetThumbnailConfig& OutThumbnailConfig, const FAssetData& InAssetData)
{
	// Get asset type color without loading the asset
	OutAssetColor = GetAssetTypeColorFromAssetData(InAssetData);
	OutThumbnailConfig.AssetTypeColorOverride = OutAssetColor;
	OutThumbnailConfig.bAllowFadeIn = true;
	OutThumbnailConfig.bForceGenericThumbnail = false;
	OutThumbnailConfig.HighlightedText = FText::FromString(InAssetData.GetObjectPathString());
	// FAssetThumbnail can be constructed from FAssetData without loading the asset
	return MakeShareable(new FAssetThumbnail(InAssetData, SSourcifyGraphInternal::AssetThumbnailSize, SSourcifyGraphInternal::AssetThumbnailSize, nullptr));
}

FAssetData SSourcifyGraph::GetAssetDataForPath(const FName& PackagePath) const
{
	IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
	
	// Get asset data by package name without loading
	TArray<FAssetData> OutAssetData;
	AssetRegistry.GetAssetsByPackageName(PackagePath, OutAssetData, true);
	
	if (OutAssetData.Num() > 0)
	{
		return OutAssetData[0];
	}
	
	return FAssetData();
}

FLinearColor SSourcifyGraph::GetAssetTypeColorFromAssetData(const FAssetData& AssetData) const
{
	FLinearColor AssetTypeColor = FLinearColor::Gray;
	
	// Get the asset class from the asset data
	FTopLevelAssetPath ClassPath = AssetData.AssetClassPath;
	
	// Try to find asset type actions for this class without loading the asset
	FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
	
	// Try to find the native class (may already be loaded as it's a class, not an instance)
	if (UClass* AssetClass = FindObject<UClass>(ClassPath))
	{
		TWeakPtr<IAssetTypeActions> AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass(AssetClass);
		if (AssetTypeActions.IsValid())
		{
			AssetTypeColor = AssetTypeActions.Pin()->GetTypeColor();
		}
	}
	else
	{
		// Fallback: map common asset types to colors based on class name
		FName ClassName = ClassPath.GetAssetName();
		if (ClassName == FName("Blueprint") || ClassName == FName("BlueprintGeneratedClass"))
		{
			AssetTypeColor = FLinearColor(0.0f, 0.4f, 0.9f); // Blue
		}
		else if (ClassName == FName("UserDefinedStruct"))
		{
			AssetTypeColor = FLinearColor(0.1f, 0.6f, 0.1f); // Green
		}
		else if (ClassName == FName("UserDefinedEnum"))
		{
			AssetTypeColor = FLinearColor(0.8f, 0.6f, 0.0f); // Orange
		}
		else if (ClassName == FName("World") || ClassName == FName("Level"))
		{
			AssetTypeColor = FLinearColor(1.0f, 0.4f, 0.4f); // Red-ish
		}
		else if (ClassName == FName("Material") || ClassName == FName("MaterialInstance") || ClassName == FName("MaterialInstanceConstant"))
		{
			AssetTypeColor = FLinearColor(0.0f, 0.8f, 0.0f); // Bright green
		}
		else if (ClassName == FName("Texture2D") || ClassName == FName("TextureCube"))
		{
			AssetTypeColor = FLinearColor(1.0f, 0.0f, 0.0f); // Red
		}
		else if (ClassName == FName("StaticMesh"))
		{
			AssetTypeColor = FLinearColor(0.2f, 0.8f, 0.8f); // Cyan
		}
		else if (ClassName == FName("SkeletalMesh"))
		{
			AssetTypeColor = FLinearColor(1.0f, 0.6f, 0.8f); // Pink
		}
		else if (ClassName == FName("DataTable"))
		{
			AssetTypeColor = FLinearColor(0.3f, 0.9f, 0.3f); // Light green
		}
		else if (ClassName == FName("WidgetBlueprint"))
		{
			AssetTypeColor = FLinearColor(0.5f, 0.7f, 0.1f); // Yellow-green
		}
	}
	
	return AssetTypeColor;
}

FText SSourcifyGraph::GetSourceControlCheckoutStatusText(const FName& AssetPath, const FAssetData& AssetData) const
{
	if (!ISourceControlModule::Get().IsEnabled())
	{
		return FText::GetEmpty();
	}

	// Look up the cached status
	const FSourceControlStatusCache* CachedStatus = SourceControlStatusCache.Find(AssetPath);
	if (!CachedStatus)
	{
		return FText::GetEmpty();
	}

	switch (CachedStatus->Status)
	{
		case ESourceControlCheckoutStatus::Loading:
			return LOCTEXT("LoadingStatus", "(loading...)");
		case ESourceControlCheckoutStatus::CheckedOutByOther:
			return FText::Format(LOCTEXT("CheckedOutByOther", "(checked out by {0})"), FText::FromString(CachedStatus->OtherUserName));
		case ESourceControlCheckoutStatus::CheckedOutByYou:
			return LOCTEXT("CheckedOutByYou", "(checked out by you)");
		case ESourceControlCheckoutStatus::AvailableForCheckout:
			return LOCTEXT("AvailableForCheckout", "(available for checkout)");
		default:
			return FText::GetEmpty();
	}
}

FSlateColor SSourcifyGraph::GetSourceControlCheckoutStatusColor(const FName& AssetPath, const FAssetData& AssetData) const
{
	if (!ISourceControlModule::Get().IsEnabled())
	{
		return FSlateColor::UseSubduedForeground();
	}

	// Look up the cached status
	const FSourceControlStatusCache* CachedStatus = SourceControlStatusCache.Find(AssetPath);
	if (!CachedStatus)
	{
		return FSlateColor::UseSubduedForeground();
	}

	switch (CachedStatus->Status)
	{
		case ESourceControlCheckoutStatus::Loading:
			return FSlateColor::UseSubduedForeground();
		case ESourceControlCheckoutStatus::CheckedOutByOther:
			return FLinearColor::Red;
		case ESourceControlCheckoutStatus::CheckedOutByYou:
		case ESourceControlCheckoutStatus::AvailableForCheckout:
			return FSlateColor::UseForeground();
		default:
			return FSlateColor::UseSubduedForeground();
	}
}

void SSourcifyGraph::OnReferencerMouseEnter(const FName& ReferencerPath)
{
	HoveredReferencerPath = ReferencerPath;
	
	// Find the nodes this referencer references and highlight them in the graph
	if (GraphEditorPtr.IsValid() && GraphObj)
	{
		if (const TArray<FName>* ReferencedNodes = ReferencerToNodeMap.Find(ReferencerPath))
		{
			UpdateNodeHighlights(*ReferencedNodes);
		}
	}
}

void SSourcifyGraph::OnReferencerMouseLeave()
{
	HoveredReferencerPath = NAME_None;
	
	// Clear the highlight overlays when mouse leaves
	ClearNodeHighlights();
}

void SSourcifyGraph::UpdateNodeHighlights(const TArray<FName>& NodePathsToHighlight)
{
	// Clear existing highlights first
	ClearNodeHighlights();
	
	if (!GraphEditorPtr.IsValid() || !GraphObj || !HighlightOverlayCanvas.IsValid())
	{
		return;
	}
	
	SGraphPanel* GraphPanel = GraphEditorPtr->GetGraphPanel();
	if (!GraphPanel)
	{
		return;
	}
	
	// Store the highlighted paths
	HighlightedNodePaths.Empty();
	for (const FName& NodePath : NodePathsToHighlight)
	{
		HighlightedNodePaths.Add(NodePath);
	}
	
	// Get the current view offset and zoom level from the graph panel
	const FVector2D ViewOffset = GraphPanel->GetViewOffset();
	const float ZoomLevel = GraphPanel->GetZoomAmount();
	
	// Create highlight widgets for each node that should be highlighted
	for (const FName& NodePath : NodePathsToHighlight)
	{
		// Find the graph node for this path
		for (UEdGraphNode* Node : GraphObj->Nodes)
		{
			if (UEdGraphNode_Sourcify* SourcifyNode = Cast<UEdGraphNode_Sourcify>(Node))
			{
				FAssetData AssetData = SourcifyNode->GetAssetData();
				if (AssetData.IsValid())
				{
					FName NodeAssetPath(*AssetData.PackageName.ToString());
					if (NodeAssetPath == NodePath)
					{
						// Get the node widget to calculate its bounds
						TSharedPtr<SGraphNode> NodeWidget = GraphPanel->GetNodeWidgetFromGuid(SourcifyNode->NodeGuid);
						if (NodeWidget.IsValid())
						{
							FVector2D NodePos(SourcifyNode->NodePosX, SourcifyNode->NodePosY);
							FVector2D NodeSize = NodeWidget->GetCachedGeometry().GetLocalSize();
							FVector2D ScreenPos = (NodePos - ViewOffset) * ZoomLevel;
							FVector2D ScreenSize = NodeSize * ZoomLevel;
							
							const float Padding = 6.0f * ZoomLevel;
							ScreenPos -= FVector2D(Padding, Padding);
							ScreenSize += FVector2D(Padding * 2, Padding * 2);
							
							TSharedRef<SSourcifyGraphInternal::SNodeHighlightBorder> HighlightWidget = 
								SNew(SSourcifyGraphInternal::SNodeHighlightBorder)
								.BorderColor(FLinearColor(1.0f, 1.0f, 1.0f, 1.0f))
								.BorderThickness(10.0f * ZoomLevel)
								.CornerRadius(8.0f * ZoomLevel);
							
							HighlightOverlayCanvas->AddSlot()
								.Position(ScreenPos)
								.Size(ScreenSize)
								[
									HighlightWidget
								];
						}
					}
				}
			}
		}
	}
}

void SSourcifyGraph::ClearNodeHighlights()
{
	HighlightedNodePaths.Empty();
	
	if (HighlightOverlayCanvas.IsValid())
	{
		HighlightOverlayCanvas->ClearChildren();
	}
}

void SSourcifyGraph::OnGraphNodeMouseEnter(const FName& NodePath)
{
	HoveredNodePath = NodePath;
	
	// Refresh the referencer list to update highlighting
	if (ReferencersListView.IsValid())
	{
		ReferencersListView->RequestListRefresh();
	}
}

void SSourcifyGraph::OnGraphNodeMouseLeave()
{
	HoveredNodePath = NAME_None;
	
	// Refresh the referencer list to update highlighting
	if (ReferencersListView.IsValid())
	{
		ReferencersListView->RequestListRefresh();
	}
}

bool SSourcifyGraph::IsReferencerHighlighted(const FName& ReferencerPath) const
{
	// Highlight if this referencer references the currently hovered node
	if (!HoveredNodePath.IsNone())
	{
		if (const TArray<FName>* FoundReferencers = NodeToReferencerMap.Find(HoveredNodePath))
		{
			return FoundReferencers->Contains(ReferencerPath);
		}
	}
	return false;
}

bool SSourcifyGraph::IsGraphNodeHighlighted(const FName& NodePath) const
{
	// Highlight if this node is referenced by the currently hovered referencer
	if (!HoveredReferencerPath.IsNone())
	{
		if (const TArray<FName>* ReferencedNodes = ReferencerToNodeMap.Find(HoveredReferencerPath))
		{
			return ReferencedNodes->Contains(NodePath);
		}
	}
	return false;
}

#undef LOCTEXT_NAMESPACE
