// Copyright (c) Microsoft Corporation. 
// Licensed under the MIT license.

#pragma once

#include "TreeItemID.h"
#include "StandinActor.h"
#include "StandInsOutlinerDragDropOp.h"

class FMenuBuilder;
class UWorld;

namespace StandInsOutliner
{
	class SStandInsOutliner;
	struct FTreeItemID;

	/** Interface used for validating movement (ie, drag/dropping) operations */
	struct IDropTarget
	{
		/** Called to test whether the specified payload can be dropped onto this tree item */
		virtual FDragValidationInfo ValidateDrop(FDragDropPayload& DraggedObjects) const = 0;

		/** Called to drop the specified objects on this item. Only called if ValidateDrop() allows. */
		virtual void OnDrop(FDragDropPayload& DraggedObjects, const FDragValidationInfo& ValidationInfo, TSharedRef<SWidget> DroppedOnWidget) = 0;
	};

	typedef TSharedRef<ITreeItem> FTreeItemRef;

	struct ITreeItem : IDropTarget, TSharedFromThis < ITreeItem >
	{
		friend SStandInsOutliner;

		enum TreeItemType
		{
			Invalid,
			StandInMeshActor,
			StaticMeshActor
		};

	public:
		/** Flag whether or not this item is expanded in the treeview */
		bool bIsExpanded;

		bool bCanGenerateContextMenu : 1;

	protected:
		/** Default constructor */
		ITreeItem()
			: bIsExpanded(false)
			, bCanGenerateContextMenu(true)
			//, Parent(nullptr)
			, Type(Invalid)
		{}
		virtual ~ITreeItem() {}

		/** This item's parent, if any. */
		//mutable TWeakPtr<ITreeItem> Parent;
		mutable TArray<TWeakPtr<ITreeItem>> Parents;
		/** Array of children contained underneath this item */
		mutable TArray<TWeakPtr<ITreeItem>> Children;

		/** Item type enum */
		mutable TreeItemType Type;

	public:
		/** Get this item's parent. Can be nullptr. */
		/*FTreeItemPtr GetParent() const
		{
			return Parent.Pin();
		}*/

		const TArray<TWeakPtr<ITreeItem>>& GetParents() const
		{
			return Parents;
		}

		/** Add a child to this item */
		void AddChild(FTreeItemRef Child)
		{
			auto ID = GetID();
			auto FoundItem = Parents.FindByPredicate([ID](TWeakPtr<ITreeItem> Item) 
				{ 
					if (!Item.IsValid())
						return false;
					return ID == Item.Pin()->GetID();
				});

			if (!FoundItem)
			{
				Child->Parents.Add(AsShared());
				
			}
			Children.Add(MoveTemp(Child));
		}

		/** Remove a child from this item */
		void RemoveChild(const FTreeItemRef& Child)
		{
			if (Children.Remove(Child))
			{
				auto ID = GetID();
				auto FoundItem = Parents.FindByPredicate([ID](TWeakPtr<ITreeItem> Item)
					{
						if (!Item.IsValid())
							return false;
						return ID == Item.Pin()->GetID();
					});

				if (FoundItem)
				{
					Child->Parents.Remove(*FoundItem);

				}
			}
		}

		/** Returns the TreeItem's type */
		const TreeItemType GetTreeItemType()
		{
			return Type;
		}

		/** Get this item's children, if any. Although we store as weak pointers, they are guaranteed to be valid. */
		FORCEINLINE const TArray<TWeakPtr<ITreeItem>>& GetChildren() const
		{
			return Children;
		}

	public:
		/** Get the raw string to display for this tree item - used for sorting */
		virtual FString GetDisplayString() const = 0;

		/** Get the name of the actor refered to for filtering */
		virtual FString GetName() const = 0;

		/** Check whether it should be possible to interact with this tree item */
		virtual bool CanInteract() const = 0;

		/** Called when this item is expanded or collapsed */
		virtual void OnExpansionChanged() {};

		/** Generate a context menu for this item. Only called if *only* this item is selected. */
		virtual void GenerateContextMenu(FMenuBuilder& MenuBuilder, SStandInsOutliner& Outliner) {};

		/** Populate the specified drag/drop payload with any relevant information for this type */
		virtual void PopulateDragDropPayload(FDragDropPayload& Payload) const = 0;

		/** Returns this TreeItem's ID */
		virtual FTreeItemID GetID() = 0;

		/** Returns the Tint used for displaying this item in the Treeview*/
		virtual FSlateColor GetTint() const
		{
			return FLinearColor(1.0f, 1.0f, 1.0f);
		}

		virtual bool IsHiddenInEditor() const = 0;
		virtual bool HasSimplygonUserData() const { return false; }
	};
};
