#include "WingPropHandle.h"
#include "WingServer.h"
#include "WingActorComponent.h"
#include "WingUtils.h"
#include "WingTypes.h"
#include "Dom/JsonValue.h"
#include "PropertyEditorModule.h"
#include "IDetailTreeNode.h"
#include "Engine/Blueprint.h"
#include "Components/Widget.h"
#include "Components/PanelSlot.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "Materials/MaterialExpression.h"
#include "UObject/EnumProperty.h"
#include "EdGraph/EdGraphPin.h"

/////////////////////////////////////////////////////////////////////////////
//
// Get Root
//
/////////////////////////////////////////////////////////////////////////////

TSharedRef<IPropertyRowGenerator> WingPropHandle::CreateGenerator()
{
	FPropertyEditorModule& Module = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
	FPropertyRowGeneratorArgs Args;
	Args.bShouldShowHiddenProperties = false;
	return Module.CreatePropertyRowGenerator(Args);
}

WingPropHandle::Root& WingPropHandle::GetRootForObject(UObject* Obj)
{
	for (Root& R : Roots)
	{
		if (R.Base == (uint8*)Obj) return R;
	}
	TSharedRef<IPropertyRowGenerator> Gen = CreateGenerator();
	Gen->SetObjects({Obj});
	Root& R = Roots.AddDefaulted_GetRef();
	R.Struct = Obj->GetClass();
	R.Base = (uint8*)Obj;
	R.End = R.Base + R.Struct->GetStructureSize();
	R.Generator = Gen;
	return R;
}

WingPropHandle::Root& WingPropHandle::GetRootForStruct(const UStruct* ScriptStruct, uint8* Data)
{
	for (Root& R : Roots)
	{
		if (R.Base == Data) return R;
	}
	TSharedRef<IPropertyRowGenerator> Gen = CreateGenerator();
	TSharedPtr<FStructOnScope> Wrapper = MakeShared<FStructOnScope>(ScriptStruct, Data);
	Gen->SetStructure(Wrapper);
	Root& R = Roots.AddDefaulted_GetRef();
	R.Struct = ScriptStruct;
	R.Base = Data;
	R.End = Data + ScriptStruct->GetStructureSize();
	R.Generator = Gen;
	return R;
}

/////////////////////////////////////////////////////////////////////////////
//
// IsInsideRootObject
//
/////////////////////////////////////////////////////////////////////////////

bool WingPropHandle::IsInsideRootObject(const Root& Root, IPropertyHandle& Handle)
{
	// Walk up to the topmost property handle that still has a property.
	// Keep the shared pointers alive so the raw pointer stays valid.
	TSharedPtr<IPropertyHandle> Held;
	IPropertyHandle* Top = &Handle;
	while (true)
	{
		TSharedPtr<IPropertyHandle> Parent = Top->GetParentHandle();
		if (!Parent.IsValid() || !Parent->GetProperty()) break;
		Held = Parent;
		Top = Held.Get();
	}

	// Get the address of the topmost property's data.
	void* Addr = nullptr;
	if (Top->GetValueData(Addr) != FPropertyAccess::Success || !Addr)
		return false;

	uint8* DataPtr = (uint8*)Addr;
	return DataPtr >= Root.Base && DataPtr < Root.End;
}

/////////////////////////////////////////////////////////////////////////////
//
// All Tree Nodes
//
/////////////////////////////////////////////////////////////////////////////

void WingPropHandle::AllTreeNodesRecursive(const TSharedRef<IDetailTreeNode>& Node, FlatTree& Out)
{
	if (Node->GetNodeType() == EDetailNodeType::Category)
	{
		TArray<TSharedRef<IDetailTreeNode>> Children;
		Node->GetChildren(Children);
		for (const TSharedRef<IDetailTreeNode>& Child : Children)
		{
			AllTreeNodesRecursive(Child, Out);
		}
		return;
	}

	Out.Add(&*Node);
}

WingPropHandle::FlatTree WingPropHandle::AllTreeNodes(Root& Root)
{
	FlatTree Result;
	for (const TSharedRef<IDetailTreeNode>& TreeRoot : Root.Generator->GetRootTreeNodes())
	{
		AllTreeNodesRecursive(TreeRoot, Result);
	}
	return Result;
}

/////////////////////////////////////////////////////////////////////////////
//
// AllProperties
//
/////////////////////////////////////////////////////////////////////////////

WingPropHandle::Handles WingPropHandle::AllProperties(Root& Root, bool RootFilter)
{
	Handles Result;
	for (IDetailTreeNode* Node : AllTreeNodes(Root))
	{
		TSharedPtr<IPropertyHandle> Handle = Node->CreatePropertyHandle();
		if (Handle.IsValid() && Handle->GetProperty())
		{
			if (RootFilter && !IsInsideRootObject(Root, *Handle))
				continue;
			Result.Add(Handle);
		}
	}
	return Result;
}

WingPropHandle::Handles WingPropHandle::AllProperties(UObject* Obj, bool RootFilter)
{
	if (!Obj) return {};
	return AllProperties(GetRootForObject(Obj), RootFilter);
}

WingPropHandle::Handles WingPropHandle::AllProperties(const UStruct* ScriptStruct, uint8* Data, bool RootFilter)
{
	if (!ScriptStruct || !Data) return {};
	return AllProperties(GetRootForStruct(ScriptStruct, Data), RootFilter);
}

/////////////////////////////////////////////////////////////////////////////
//
// Named Property
//
/////////////////////////////////////////////////////////////////////////////

TSharedPtr<IPropertyHandle> WingPropHandle::TryNamedProperty(Root& Root, FName Name, bool RootFilter)
{
	for (IDetailTreeNode* Node : AllTreeNodes(Root))
	{
		if (Node->GetNodeName() != Name) continue;
		TSharedPtr<IPropertyHandle> Handle = Node->CreatePropertyHandle();
		if (Handle.IsValid() && (!RootFilter || IsInsideRootObject(Root, *Handle)))
			return Handle;
	}
	return nullptr;
}

TSharedPtr<IPropertyHandle> WingPropHandle::TryNamedProperty(UObject* Obj, FName Name, bool RootFilter)
{
	if (!Obj) return nullptr;
	return TryNamedProperty(GetRootForObject(Obj), Name, RootFilter);
}

TSharedPtr<IPropertyHandle> WingPropHandle::TryNamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter)
{
	if (!ScriptStruct || !Data) return nullptr;
	return TryNamedProperty(GetRootForStruct(ScriptStruct, Data), Name, RootFilter);
}

TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(UObject* Obj, FName Name, bool RootFilter)
{
	TSharedPtr<IPropertyHandle> Result = TryNamedProperty(Obj, Name, RootFilter);
	if (!Result)
		UWingServer::Printf(TEXT("ERROR: Property '%s' not found\n"), *Name.ToString());
	return Result;
}

TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter)
{
	TSharedPtr<IPropertyHandle> Result = TryNamedProperty(ScriptStruct, Data, Name, RootFilter);
	if (!Result)
		UWingServer::Printf(TEXT("ERROR: Property '%s' not found\n"), *Name.ToString());
	return Result;
}

/////////////////////////////////////////////////////////////////////////////
//
// GetDetails
//
/////////////////////////////////////////////////////////////////////////////

WingPropHandle::Handles WingPropHandle::GetDetails(UObject* Obj, bool Mutable)
{
	bool RootFilter = false;

	if (!Obj) return {};

	// Blueprints: redirect to the generated class CDO.
	if (UBlueprint* BP = Cast<UBlueprint>(Obj))
	{
		if (!BP->GeneratedClass)
		{
			UWingServer::Printf(TEXT("ERROR: Blueprint '%s' has no GeneratedClass\n"), *Obj->GetName());
			return {};
		}
		Obj = BP->GeneratedClass->GetDefaultObject();
	}

	// UWingComponentReference is a class of our own creation, containing
	// a pointer to a blueprint and a component name.  Sometimes, the
	// component is inherited.  If so, and if you want to mutate it, you
	// first have to create the override template in the child.  If you're
	// not mutating, you can just use the existing inherited template.
	if (UWingComponentReference* Ref = Cast<UWingComponentReference>(Obj))
	{
		Obj = Mutable ? Ref->GetMutableTemplate() : Ref->GetImmutableTemplate();
		if (!Obj)
		{
			UWingServer::Printf(TEXT("ERROR: Component '%s' has no template\n"), *Ref->VariableName.ToString());
			return {};
		}
	}

	// Actors have components, which flood the property listing with hundreds
	// of confusing additional properties.
	if (Cast<AActor>(Obj)) RootFilter = true;

	// Fetch the handles.
	Handles Result = AllProperties(Obj, RootFilter);
	
	// Material graph nodes: also collect expression properties.
	if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Obj))
	{
		if (UMaterialExpression* Expr = MatNode->MaterialExpression)
		{
			Result.Append(AllProperties(Expr, true));
		}
	}

	// Widgets: hide the Slot property, add slot properties.
	// if (UWidget* Widget = Cast<UWidget>(Obj))
	// {
	// 	Result.RemoveAll([](const TSharedPtr<IPropertyHandle>& H)
	// 	{
	// 		return H->GetProperty()->GetFName() == TEXT("Slot");
	// 	});
	// 	if (UPanelSlot* Slot = Widget->Slot)
	// 	{
	// 		Result.Append(AllProperties(Slot, false));
	// 	}
	// }

	return Result;
}
/////////////////////////////////////////////////////////////////////////////
//
// Organize by Name
//
/////////////////////////////////////////////////////////////////////////////

bool WingPropHandle::OrganizeByName(const Handles &HList, TMap<FName, TSharedPtr<IPropertyHandle>> &Result)
{
	Result.Empty();
	TSet<FName> DuplicateNames;
	for (const TSharedPtr<IPropertyHandle> &H : HList)
	{
		FName Name = H->GetProperty()->GetFName();
		if (Result.Contains(Name)) DuplicateNames.Add(Name);
		else Result.Add(Name, H);
	}
	if (DuplicateNames.IsEmpty()) return true;
	UWingServer::Print(TEXT("More than one property with name:"));
	for (FName DupName : DuplicateNames)
	{
		UWingServer::Printf(TEXT(" %s"), *WingUtils::ExternalizeID(DupName));
	}
	UWingServer::Print(TEXT("\n"));
	return false;
}

/////////////////////////////////////////////////////////////////////////////
//
// GetText / SetText
//
/////////////////////////////////////////////////////////////////////////////

bool WingPropHandle::IsPinTypeProperty(FProperty* Prop)
{
	FStructProperty* StructProp = CastField<FStructProperty>(Prop);
	return StructProp && StructProp->Struct == FEdGraphPinType::StaticStruct();
}

FString WingPropHandle::GetText(IPropertyHandle& Handle)
{
	// Pin types: use our human-readable format.
	if (IsPinTypeProperty(Handle.GetProperty()))
	{
		void* Data = nullptr;
		if (Handle.GetValueData(Data) != FPropertyAccess::Success || !Data)
			UE_LOG(LogTemp, Fatal, TEXT("GetValueData failed for pin type property '%s'"),
				*Handle.GetProperty()->GetName());
		return UWingTypes::TypeToText(*static_cast<FEdGraphPinType*>(Data));
	}

	// Class properties: use our human-readable type names.
	if (CastField<FClassProperty>(Handle.GetProperty()))
	{
		UObject* ClassObj = nullptr;
		if (Handle.GetValue(ClassObj) != FPropertyAccess::Success)
			UE_LOG(LogTemp, Fatal, TEXT("GetValue failed for class property '%s'"),
				*Handle.GetProperty()->GetName());
		if (!ClassObj) return TEXT("None");
		return UWingTypes::TypeToText(ClassObj);
	}

	FString Result;
	if (Handle.GetValueAsFormattedString(Result) != FPropertyAccess::Success)
		UE_LOG(LogTemp, Fatal, TEXT("GetValueAsFormattedString failed for property '%s'"),
			*Handle.GetProperty()->GetName());
	return Result;
}

bool WingPropHandle::SetText(IPropertyHandle& Handle, const FString& Text)
{
	FProperty* Prop = Handle.GetProperty();

	// Pin types: parse with our type parser.
	if (IsPinTypeProperty(Prop))
	{
		FEdGraphPinType PinType;
		UWingTypes::Requirements Req;
		Req.BlueprintType = true;
		Req.Blueprintable = false;
		Req.AllowContainer = true;
		if (!UWingTypes::TextToType(Text, PinType, Req)) return false;
		void* Data = nullptr;
		if (Handle.GetValueData(Data) != FPropertyAccess::Success || !Data)
		{
			UWingServer::Printf(TEXT("ERROR: Cannot access data for property '%s'\n"),
				*WingUtils::FormatName(Prop));
			return false;
		}
		Handle.NotifyPreChange();
		*static_cast<FEdGraphPinType*>(Data) = PinType;
		Handle.NotifyPostChange(EPropertyChangeType::ValueSet);
		return true;
	}

	// Class properties: parse with our type parser.
	if (FClassProperty* ClassProp = CastField<FClassProperty>(Prop))
	{
		UObject* Class = nullptr;
		if (!Text.IsEmpty())
		{
			UWingTypes::Requirements Req;
			Req.BlueprintType = true;
			Req.Blueprintable = false;
			Req.IsChildOf = ClassProp->MetaClass;
			Class = UWingTypes::TextToOneObjectType(Text, Req);
			if (!Class) return false;
		}
		if (Handle.SetValue(Class) != FPropertyAccess::Success)
		{
			UWingServer::Printf(TEXT("ERROR: Failed to set class property '%s'\n"),
				*WingUtils::FormatName(Prop));
			return false;
		}
		return true;
	}

	// Enums: canonicalize the string with our smarter prefix matching.
	FString Value = Text;
	UEnum* Enum = nullptr;
	if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
		Enum = ByteProp->Enum;
	if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
		Enum = EnumProp->GetEnum();
	if (Enum != nullptr)
	{
		int64 EnumValue;
		if (!WingUtils::StringToEnum(Enum, Value, EnumValue)) return false;
		Value = Enum->GetNameStringByValue(EnumValue);
	}

	FPropertyAccess::Result Result = Handle.SetValueFromFormattedString(Value);
	if (Result != FPropertyAccess::Success)
	{
		UWingServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
			*Value, *WingUtils::FormatName(Prop), *Prop->GetCPPType());
		return false;
	}
	return true;
}

/////////////////////////////////////////////////////////////////////////////
//
// SetJson
//
/////////////////////////////////////////////////////////////////////////////

bool WingPropHandle::SetJson(IPropertyHandle& Handle, const TSharedPtr<FJsonValue>& JsonValue)
{
	FPropertyAccess::Result Result;

	switch (JsonValue->Type)
	{
	case EJson::String:
		return SetText(Handle, JsonValue->AsString());

	case EJson::Boolean:
		Result = Handle.SetValue(JsonValue->AsBool());
		break;

	case EJson::Number:
		Result = Handle.SetValue(JsonValue->AsNumber());
		break;

	default:
		Result = FPropertyAccess::Fail;
		break;
	}

	if (Result != FPropertyAccess::Success)
	{
		UWingServer::Printf(TEXT("ERROR: Failed to set property '%s'\n"),
			*WingUtils::FormatName(Handle.GetProperty()));
		return false;
	}
	return true;
}

bool WingPropHandle::PopulateFromJson(TArray<TSharedPtr<IPropertyHandle>>& Props, const FJsonObject& Json, bool AllOptional)
{
	bool Ok = true;

	// Organize the properties by name.
	TMap<FName, TSharedPtr<IPropertyHandle>> OrganizedProps;
	if (!OrganizeByName(Props, OrganizedProps)) Ok = false;

	// Parse the keys in the json, make sure they're syntactically valid and
	// that they match the names of actual properties, and that there are no dups.
	TSet<FName> Specified;
	for (const auto& KV : Json.Values)
	{
		FName Name = WingUtils::CheckInternalizeID(KV.Key);
		if (Name.IsNone()) { Ok = false; continue; }
		if (!OrganizedProps.Contains(Name))
		{
			UWingServer::Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key);
			Ok = false;
		}
		if (!WingUtils::FindNoDuplicateName(Specified, Name, TEXT("parameter"))) Ok = false;
	}

	// Make sure that all required properties have been specified.
	if (!AllOptional)
	{
		for (const TSharedPtr<IPropertyHandle> &H : Props)
		{
			if (H->HasMetaData(TEXT("Optional"))) continue;
			FName Name = H->GetProperty()->GetFName();
			if (!Specified.Contains(Name))
			{
				UWingServer::Printf(TEXT("Required parameter %s not specified\n"),
					*WingUtils::ExternalizeID(Name));
				Ok = false;
			}
		}
	}

	// If anything is wrong, return early without setting anything.
	if (!Ok) return false;

	// Populate each property from JSON.  This could fail too, but at this
	// point, we're committed.
	for (const auto& KV : Json.Values)
	{
		FName Name = WingUtils::CheckInternalizeID(KV.Key);
		if (!SetJson(*OrganizedProps[Name], KV.Value)) Ok = false;
	}
	return Ok;
}


/////////////////////////////////////////////////////////////////////////////
//
// Print
//
/////////////////////////////////////////////////////////////////////////////

void WingPropHandle::Print(IPropertyHandle& Handle, FStringBuilderBase& Out)
{
	FString Value = GetText(Handle);
	Value.ReplaceInline(TEXT("\r"), TEXT(" "));
	Value.ReplaceInline(TEXT("\n"), TEXT(" "));
	if (Value.Len() > 100) Value = Value.Left(100) + TEXT("...");

	bool bEditable = !Handle.IsEditConst();

	Out.Appendf(TEXT("    %s %s %s = %s\n"),
		bEditable ? TEXT("editable") : TEXT("readonly"),
		*UWingTypes::TypeToText(Handle.GetProperty()),
		*WingUtils::FormatName(Handle),
		*Value);
}
