#pragma once

#include "CoreMinimal.h"
#include "IPropertyRowGenerator.h"
#include "PropertyHandle.h"

class FJsonObject;
class FJsonValue;

// WingPropHandle: A module that provides easy access to
// IPropertyHandle objects.  To use this module, construct a
// WingPropHandle, then use it to fetch properties from
// objects.  The property handles returned are valid until
// the WingPropHandle is destroyed.
//
// In UE Wingman, we are encouraging the use of
// IPropertyHandle over FProperty.  The FProperty API sucks:
// it's buggy and unreliable.  Use IPropertyHandle instead.
//

class WingPropHandle
{
public:
	using Handles = TArray<TSharedPtr<IPropertyHandle>>;
	using FlatTree = TArray<IDetailTreeNode *, TInlineAllocator<500>>;

	WingPropHandle() {}

	// Get all properties of a UObject. Returns the handles.
	// If RootFilter is true, only properties inside the root object are returned.
	Handles AllProperties(UObject* Obj, bool RootFilter);

	// Get all properties of a struct. Does not copy — the data
	// pointer must remain valid for the lifetime of this object.
	// If RootFilter is true, only properties inside the root object are returned.
	Handles AllProperties(const UStruct* ScriptStruct, uint8* Data, bool RootFilter);

	// Get a single named property from a UObject.
	// If RootFilter is true, only properties inside the root object are returned.
	TSharedPtr<IPropertyHandle> TryNamedProperty(UObject* Obj, FName Name, bool RootFilter);

	// Get a single named property from a struct.
	// If RootFilter is true, only properties inside the root object are returned.
	TSharedPtr<IPropertyHandle> TryNamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter);

	// Like TryNamedProperty, but prints an error if the property is not found.
	TSharedPtr<IPropertyHandle> NamedProperty(UObject* Obj, FName Name, bool RootFilter);
	TSharedPtr<IPropertyHandle> NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter);

	// Get "details panel" properties for an object. Handles special
	// cases: blueprints (redirects to CDO), component references
	// (redirects to template), material graph nodes (includes
	// expression properties), widgets (includes slot properties).
	Handles GetDetails(UObject* Obj, bool Mutable);

	// Organize properties by name.  If there's more than one property
	// of a given name, print an error and returns false, and returns a map
	// that contains one of the two duplicates.
	static bool OrganizeByName(const Handles &H, TMap<FName, TSharedPtr<IPropertyHandle>> &Result);

	// Get/set text.  Compared to the built in methods for getting and setting
	// text, these support more concise enum values, FEdGraphPinType, and
	// more concise Class properties.
	static FString GetText(IPropertyHandle& Handle);
	static bool SetText(IPropertyHandle& Handle, const FString& Text);

	// Store a Json value into a property handle.  The Json value must be
	// a string, number, or boolean.
	static bool SetJson(IPropertyHandle& Handle, const TSharedPtr<FJsonValue>& JsonValue);

	// Populate a whole bunch of properties from a Json object.  If
	// AllOptional is true, the Json may supply a subset of the properties.
	// If not, the Json must supply all of them, excepting properties that
	// are explicitly marked Optional.
	static bool PopulateFromJson(TArray<TSharedPtr<IPropertyHandle>>& Props, const FJsonObject& Json, bool AllOptional);

	// Print a single property in a standardized format:
	//     editable|readonly Type Name = Value
	static void Print(IPropertyHandle& Handle, FStringBuilderBase& Out);

private:
	struct Root
	{
		const UStruct* Struct = nullptr;
		uint8* Base = nullptr;
		uint8* End = nullptr;
		TSharedPtr<IPropertyRowGenerator> Generator;
	};

	static bool IsPinTypeProperty(FProperty* Prop);
	static bool IsInsideRootObject(const Root& Root, IPropertyHandle& Handle);
	TArray<Root> Roots;

	static TSharedRef<IPropertyRowGenerator> CreateGenerator();
	Root& GetRootForObject(UObject* Obj);
	Root& GetRootForStruct(const UStruct* ScriptStruct, uint8* Data);
	static void AllTreeNodesRecursive(const TSharedRef<IDetailTreeNode>& Node, FlatTree& Out);
	static FlatTree AllTreeNodes(Root& Root);

	Handles AllProperties(Root& Root, bool RootFilter);
	static TSharedPtr<IPropertyHandle> TryNamedProperty(Root& Root, FName Name, bool RootFilter);
};
