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

#include "Customizations/InworldCharacterComponentDetailsCustomization.h"

#include "Components/InworldBaseCharacterComponent.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "Engine/DataTable.h"
#include "IPropertyUtilities.h"
#include "Widgets/Input/SComboBox.h"
#include "Widgets/Text/STextBlock.h"

#define LOCTEXT_NAMESPACE "InworldCharacterComponentDetails"

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

void FInworldCharacterComponentDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
	DetailBuilderPtr = &DetailBuilder;

	TArray<TWeakObjectPtr<UObject>> ObjectsBeingCustomized;
	DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized);

	if (ObjectsBeingCustomized.Num() != 1)
	{
		return;
	}

	TargetComponent = Cast<UInworldBaseCharacterComponent>(ObjectsBeingCustomized[0].Get());
	if (!TargetComponent.IsValid())
	{
		return;
	}

	IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(TEXT("Inworld"), FText::FromString(TEXT("Inworld")));

	// Find and then hide the "Id" property that is auto-generated by the DataTable
	TArray<TSharedRef<IPropertyHandle>> DefaultProperties;
	IDetailCategoryBuilder& InworldCategory = DetailBuilder.EditCategory("Inworld");
	InworldCategory.GetDefaultProperties(DefaultProperties, true, true);

	for (const TSharedRef<IPropertyHandle>& PropertyHandle : DefaultProperties)
	{
		if (PropertyHandle->IsValidHandle() && PropertyHandle->GetProperty()->GetFName() == FName("Id"))
		{
			DetailBuilder.HideProperty(PropertyHandle);
			Category.AddProperty(PropertyHandle);
			break;
		}
	}

	TSharedPtr<IPropertyHandle> UseDataTableHandle =
		DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UInworldBaseCharacterComponent, bUseDataTable));
	TSharedPtr<IPropertyHandle> DataTableHandle =
		DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UInworldBaseCharacterComponent, CharacterDataTable));
	CharacterIDHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UInworldBaseCharacterComponent, CharacterID));

	DetailBuilder.HideProperty(UseDataTableHandle);
	DetailBuilder.HideProperty(DataTableHandle);
	DetailBuilder.HideProperty(CharacterIDHandle);

	auto GetDataTableVisibility = [UseDataTableHandle]() -> EVisibility
	{
		bool bIsVisible = false;
		if (UseDataTableHandle->IsValidHandle())
		{
			UseDataTableHandle->GetValue(bIsVisible);
		}
		return bIsVisible ? EVisibility::Visible : EVisibility::Collapsed;
	};

	Category.AddProperty(UseDataTableHandle);
	Category.AddProperty(DataTableHandle).Visibility(TAttribute<EVisibility>::CreateLambda(GetDataTableVisibility));

	DataTableHandle->SetOnPropertyValueChanged(
		FSimpleDelegate::CreateSP(this, &FInworldCharacterComponentDetailsCustomization::OnCharacterDataTableChanged));

	CharacterIDOptions.Empty();
	UDataTable* CurrentDataTable = TargetComponent->CharacterDataTable.LoadSynchronous();
	if (CurrentDataTable)
	{
		TArray<FName> RowNames = CurrentDataTable->GetRowNames();
		for (const FName& RowName : RowNames)
		{
			CharacterIDOptions.Add(MakeShared<FName>(RowName));
		}
	}

	Category.AddCustomRow(CharacterIDHandle->GetPropertyDisplayName())
		.IsEnabled(TAttribute<bool>::Create(
			[this, CurrentDataTable]()
			{
				if (TargetComponent.IsValid())
				{
					return TargetComponent->bUseDataTable && CurrentDataTable != nullptr;
				}
				return false;
			}))
		.Visibility(TAttribute<EVisibility>::CreateLambda(GetDataTableVisibility))
		.NameContent()
			[SNew(STextBlock).Text(CharacterIDHandle->GetPropertyDisplayName()).Font(IDetailLayoutBuilder::GetDetailFont())]
		.ValueContent()[SNew(SComboBox<TSharedPtr<FName>>)
							.OptionsSource(&CharacterIDOptions)
							.OnSelectionChanged(this, &FInworldCharacterComponentDetailsCustomization::OnCharacterIDChanged)
							.OnGenerateWidget(this, &FInworldCharacterComponentDetailsCustomization::OnGenerateCharacterIDWidget)
							.InitiallySelectedItem(
								[this]() -> TSharedPtr<FName>
								{
									TSharedPtr<FName>* FoundItem = CharacterIDOptions.FindByPredicate(
										[this](const TSharedPtr<FName>& Option)
										{
											return Option.IsValid() && TargetComponent.IsValid() &&
												   *Option == TargetComponent->CharacterID;
										});
									return FoundItem ? *FoundItem : nullptr;
								}())[SNew(STextBlock)
										 .Text(this, &FInworldCharacterComponentDetailsCustomization::GetCurrentCharacterIDText)
										 .Font(IDetailLayoutBuilder::GetDetailFont())]];
}

void FInworldCharacterComponentDetailsCustomization::OnCharacterDataTableChanged()
{
	if (DetailBuilderPtr)
	{
		DetailBuilderPtr->ForceRefreshDetails();
	}
}

TSharedRef<SWidget> FInworldCharacterComponentDetailsCustomization::OnGenerateCharacterIDWidget(TSharedPtr<FName> InItem) const
{
	return SNew(STextBlock).Text(FText::FromName(*InItem));
}

FText FInworldCharacterComponentDetailsCustomization::GetCurrentCharacterIDText() const
{
	if (TargetComponent.IsValid() && CharacterIDHandle.IsValid())
	{
		FName CurrentValue;
		if (CharacterIDHandle->GetValue(CurrentValue) == FPropertyAccess::Success && CurrentValue != NAME_None)
		{
			bool bOptionIsValid = CharacterIDOptions.ContainsByPredicate(
				[&CurrentValue](const TSharedPtr<FName>& Option)
				{
					return Option.IsValid() && *Option == CurrentValue;
				});

			if (bOptionIsValid)
			{
				return FText::FromName(CurrentValue);
			}
		}
	}
	return LOCTEXT("None", "None");
}

void FInworldCharacterComponentDetailsCustomization::OnCharacterIDChanged(
	TSharedPtr<FName> NewSelection, ESelectInfo::Type SelectInfo)
{
	if (CharacterIDHandle.IsValid() && NewSelection.IsValid())
	{
		CharacterIDHandle->SetValue(*NewSelection);
	}
}

#undef LOCTEXT_NAMESPACE
