// Copyright Dominik Pavlicek 2023. All Rights Reserved.

#include "SComboActionSearch.h"

#include "Ed/AssetEditor_ComboActionGraph.h"
#include "Framework/Commands/GenericCommands.h"
#include "Search/ComboActionSearchManager.h"
#include "Widgets/Input/SSearchBox.h"

#define LOCTEXT_NAMESPACE "SComboActionSearch"


void SComboActionSearch::Construct(const FArguments &InArgs, const TSharedPtr<FAssetEditor_ComboActionGraph> &InDialogueEditor)
{
	this->ComboActionEditorPtr = InDialogueEditor;
	this->HostTab = InArgs._ContainingTab;

	if (this->HostTab.IsValid())
	{
		this->HostTab.Pin()->SetOnTabClosed(SDockTab::FOnTabClosedCallback::CreateSP(this, &SComboActionSearch::HandleHostTabClosed));
	}

	this->ChildSlot
	[
		SAssignNew(this->MainVerticalBoxWidget, SVerticalBox)

		// Top bar, search
		+SVerticalBox::Slot()
		.AutoHeight()
		[
			SNew(SHorizontalBox)

			// Search field
			+SHorizontalBox::Slot()
			.FillWidth(1)
			[
				SAssignNew(this->SearchTextBoxWidget, SSearchBox)
				.HintText(LOCTEXT("ActionSearchHint", "Enter searched text..."))
				.OnTextChanged(this, &SComboActionSearch::HandleSearchTextChanged)
				.OnTextCommitted(this, &SComboActionSearch::HandleSearchTextCommitted)
				.Visibility(EVisibility::Visible)
			]

			// Filter Options
			+SHorizontalBox::Slot()
			.AutoWidth()
			.Padding(2.0f, 2.0f)
			[
				SNew(SComboButton)
				.ComboButtonStyle(FAppStyle::Get(), "GenericFilters.ComboButtonStyle")
				.ForegroundColor(FLinearColor::White)
				.ContentPadding(0)
				.ToolTipText(LOCTEXT("Filters_Tooltip", "Filter options"))
				.OnGetMenuContent(this, &SComboActionSearch::FillFilterEntries)
				.HasDownArrow(true)
				.ContentPadding(FMargin(1, 0))
				.ButtonContent()
				[
					SNew(SHorizontalBox)
					+ SHorizontalBox::Slot()
					.AutoWidth()
					[
						SNew(STextBlock)
						.TextStyle(FAppStyle::Get(), "GenericFilters.TextStyle")
						.Font(FAppStyle::Get().GetFontStyle("FontAwesome.9"))
						.Text(FText::FromString(FString(TEXT("\xf0b0"))) )
					]
					+SHorizontalBox::Slot()
					.AutoWidth()
					.Padding(2, 0, 0, 0)
					[
						SNew(STextBlock)
						.TextStyle(FAppStyle::Get(), "GenericFilters.TextStyle")
						.Text(LOCTEXT("Filters", "Filters"))
					]
				]
			]
		]

		// Results tree
		+SVerticalBox::Slot()
		.FillHeight(1.0f)
		.Padding(0.0f, 4.0f, 0.0f, 0.0f)
		[
			SNew(SBorder)
			.BorderImage(FAppStyle::GetBrush("Menu.Background"))
			[
				SAssignNew(this->TreeView, STreeView<TSharedPtr<FComboActionSearchResult>>)
				.ItemHeight(24)
				.TreeItemsSource(&this->ItemsFound)
				.OnGenerateRow(this, &SComboActionSearch::HandleGenerateRow)
				.OnGetChildren(this, &SComboActionSearch::HandleGetChildren)
				.OnMouseButtonDoubleClick(this, &SComboActionSearch::HandleTreeSelectionDoubleClicked)
				.SelectionMode(ESelectionMode::Multi)
				.OnContextMenuOpening(this, &SComboActionSearch::HandleContextMenuOpening)
			]
		]
	];
}

void SComboActionSearch::FocusForUse(const FComboActionSearchFilter &SearchFilter, bool bSelectFirstResult)
{
	FWidgetPath FilterTextBoxWidgetPath;
	FSlateApplication::Get().GeneratePathToWidgetUnchecked(SearchTextBoxWidget.ToSharedRef(), FilterTextBoxWidgetPath);
	
	FSlateApplication::Get().SetKeyboardFocus(FilterTextBoxWidgetPath, EFocusCause::SetDirectly);
	
	if (!SearchFilter.SearchString.IsEmpty())
	{
		SearchTextBoxWidget->SetText(FText::FromString(SearchFilter.SearchString));
		this->MakeSearchQuery(SearchFilter);
		
		if (bSelectFirstResult && this->ItemsFound.Num())
		{
			auto ItemToFocusOn = this->ItemsFound[0];
			
			while (ItemToFocusOn->HasChildren())
			{
				ItemToFocusOn = ItemToFocusOn->GetChildren()[0];
			}
			this->TreeView->SetSelection(ItemToFocusOn);
			ItemToFocusOn->OnClick(this->ComboActionEditorPtr);
		}
	}
}

void SComboActionSearch::MakeSearchQuery(const FComboActionSearchFilter &SearchFilter)
{
	this->SearchTextBoxWidget->SetText(FText::FromString(SearchFilter.SearchString));
	
	if (this->ItemsFound.Num())
	{
		this->TreeView->RequestScrollIntoView(this->ItemsFound[0]);
	}
	this->ItemsFound.Empty();
	
	if (SearchFilter.SearchString.IsEmpty())
	{
		return;
	}

	this->HighlightText = FText::FromString(SearchFilter.SearchString);
	this->RootSearchResult = MakeShared<FComboActionSearchResult_RootNode>();

	if (this->ComboActionEditorPtr.IsValid())
	{
		FComboActionSearchManager::Get()->QuerySingleAction(SearchFilter, this->ComboActionEditorPtr.Pin()->GetEditingGraphSafe(), this->RootSearchResult);
		
		const TArray<TSharedPtr<FComboActionSearchResult>> &Children = this->RootSearchResult->GetChildren();
		if (Children.Num() == 1 && Children[0].IsValid())
		{
			// we must ensure reference is created so its not garbage collected, usually resulting in crash!
			TSharedPtr<FComboActionSearchResult> TempChild = Children[0];
			this->RootSearchResult = TempChild;
			this->RootSearchResult->ClearParent();
		}
	}

	this->ItemsFound = this->RootSearchResult->GetChildren();
	if (this->ItemsFound.Num() == 0)
	{
		this->ItemsFound.Add(MakeShared<FComboActionSearchResult>(LOCTEXT("ActionSearchNoResults", "No Results found"), this->RootSearchResult));
		this->HighlightText = FText::GetEmpty();\
	}
	else
	{
		this->RootSearchResult->ExpandAllChildren(this->TreeView);
	}

	this->TreeView->RequestTreeRefresh();
}

FName SComboActionSearch::GetHostTabId() const
{
	const TSharedPtr<SDockTab> HostTabPtr = this->HostTab.Pin();
	if (HostTabPtr.IsValid())
	{
		return HostTabPtr->GetLayoutIdentifier().TabType;
	}

	return NAME_None;
}

void SComboActionSearch::CloseHostTab()
{
	const TSharedPtr<SDockTab> HostTabPtr = this->HostTab.Pin();
	if (HostTabPtr.IsValid())
	{
		HostTabPtr->RequestCloseTab();
	}
}

void SComboActionSearch::HandleHostTabClosed(TSharedRef<SDockTab> DockTab)
{
	// Clear cache
}

void SComboActionSearch::HandleSearchTextChanged(const FText& Text)
{
	this->CurrentFilter.SearchString = Text.ToString();
}

void SComboActionSearch::HandleSearchTextCommitted(const FText& Text, ETextCommit::Type CommitType)
{
	if (Text.IsEmpty())
	{
		this->TreeView->RequestTreeRefresh();
	}
	
	if (CommitType == ETextCommit::OnEnter)
	{
		this->CurrentFilter.SearchString = Text.ToString();
		this->MakeSearchQuery(this->CurrentFilter);
	}
}

void SComboActionSearch::HandleGetChildren(TSharedPtr<FComboActionSearchResult> InItem, TArray<TSharedPtr<FComboActionSearchResult>> &OutChildren)
{
	OutChildren += InItem->GetChildren();
}

void SComboActionSearch::HandleTreeSelectionDoubleClicked(TSharedPtr<FComboActionSearchResult> Item)
{
	if (Item.IsValid())
	{
		Item->OnClick(this->ComboActionEditorPtr);
	}
}

TSharedRef<ITableRow> SComboActionSearch::HandleGenerateRow(TSharedPtr<FComboActionSearchResult> InItem, const TSharedRef<STableViewBase> &OwnerTable)
{
	// Normal entry
	FText CommentText = FText::GetEmpty();
	if (!InItem->GetCommentString().IsEmpty())
	{
		FFormatNamedArguments Args;
		Args.Add(TEXT("Comment"), FText::FromString(InItem->GetCommentString()));
		CommentText = FText::Format(LOCTEXT("NodeComment", "{Comment}"), Args);
	}

	FFormatNamedArguments Args;
	Args.Add(TEXT("Category"), InItem->GetCategory());
	Args.Add(TEXT("DisplayTitle"), InItem->GetDisplayText());
	const FText Tooltip = FText::Format(LOCTEXT("DialogueResultSearchToolTip", "{Category} : {DisplayTitle}"), Args);

	return SNew(STableRow<TSharedPtr<FComboActionSearchResult>>, OwnerTable)
		[
			SNew(SHorizontalBox)

			// Icon
			+SHorizontalBox::Slot()
			.VAlign(EVerticalAlignment::VAlign_Center)
			.AutoWidth()
			[
				InItem->CreateIcon()
			]

			// Display text
			+SHorizontalBox::Slot()
			.AutoWidth()
			.VAlign(EVerticalAlignment::VAlign_Center)
			.Padding(2,0)
			[
				SNew(STextBlock)
				.Text(InItem.Get(), &FComboActionSearchResult::GetDisplayText)
				.HighlightText(HighlightText)
				.ToolTipText(Tooltip)
			]

			// Comment Block
			+SHorizontalBox::Slot()
			.FillWidth(1)
			.HAlign(EHorizontalAlignment::HAlign_Right)
			.VAlign(EVerticalAlignment::VAlign_Center)
			.Padding(2,0)
			[
				SNew(STextBlock)
				.Text(CommentText)
				.ColorAndOpacity(FLinearColor::Yellow)
			]
		];
}

TSharedPtr<SWidget> SComboActionSearch::HandleContextMenuOpening()
{
	const bool bShouldCloseWindowAfterMenuSelection = true;
	FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, this->CommandList);

	MenuBuilder.BeginSection("BasicOperations");
	{
		MenuBuilder.AddMenuEntry(FGenericCommands::Get().SelectAll);
		MenuBuilder.AddMenuEntry(FGenericCommands::Get().Copy);
	}

	return MenuBuilder.MakeWidget();
}

TSharedRef<SWidget> SComboActionSearch::FillFilterEntries()
{
	FMenuBuilder MenuBuilder(true, nullptr);
	MenuBuilder.AddMenuEntry
	(
		LOCTEXT("IncludeNodeTitle", "Include Node Title"),
		LOCTEXT("IncludeNodeTitle_ToolTip", "Include Node Titles in the search result"),
		FSlateIcon(),
		FUIAction(
			FExecuteAction::CreateLambda([this]()
			{
				this->CurrentFilter.bIncludeNodeTitle = !this->CurrentFilter.bIncludeNodeTitle;
				this->MakeSearchQuery(this->CurrentFilter);
			}),
			FCanExecuteAction(),
			FIsActionChecked::CreateLambda([this]() -> bool
			{
				return this->CurrentFilter.bIncludeNodeTitle;
			})
		),
		NAME_None,
		EUserInterfaceActionType::ToggleButton
	);
	MenuBuilder.AddMenuEntry
	(
		LOCTEXT("IncludeNodeType", "Include Node Type"),
		LOCTEXT("IncludeNodeType_ToolTip", "Include Node Type in the search result"),
		FSlateIcon(),
		FUIAction(
			FExecuteAction::CreateLambda([this]()
			{
				this->CurrentFilter.bIncludeNodeType = !this->CurrentFilter.bIncludeNodeType;
				this->MakeSearchQuery(this->CurrentFilter);
			}),
			FCanExecuteAction(),
			FIsActionChecked::CreateLambda([this]() -> bool
			{
				return this->CurrentFilter.bIncludeNodeType;
			})
		),
		NAME_None,
		EUserInterfaceActionType::ToggleButton
	);
	MenuBuilder.AddMenuEntry
	(
		LOCTEXT("IncludeNodeData", "Include Node Data Row"),
		LOCTEXT("IncludeNodeDecoratorsTypes_ToolTip", "Include Node Data Row in the search result"),
		FSlateIcon(),
		FUIAction(
			FExecuteAction::CreateLambda([this]()
			{
				this->CurrentFilter.bIncludeNodeData = !this->CurrentFilter.bIncludeNodeData;
				this->MakeSearchQuery(this->CurrentFilter);
			}),
			FCanExecuteAction(),
			FIsActionChecked::CreateLambda([this]() -> bool
			{
				return this->CurrentFilter.bIncludeNodeData;
			})
		),
		NAME_None,
		EUserInterfaceActionType::ToggleButton
	);
	MenuBuilder.AddMenuEntry
	(
		LOCTEXT("IncludeNodeGUID", "Include Node GUID"),
		LOCTEXT("IncludeNodeGUID_ToolTip", "Include Node GUID in the search result"),
		FSlateIcon(),
		FUIAction(
			FExecuteAction::CreateLambda([this]()
			{
				this->CurrentFilter.bIncludeNodeGUID = !this->CurrentFilter.bIncludeNodeGUID;
				this->MakeSearchQuery(this->CurrentFilter);
			}),
			FCanExecuteAction(),
			FIsActionChecked::CreateLambda([this]() -> bool
			{
				return this->CurrentFilter.bIncludeNodeGUID;
			})
		),
		NAME_None,
		EUserInterfaceActionType::ToggleButton
	);

	return MenuBuilder.MakeWidget();
}

#undef LOCTEXT_NAMESPACE