Utils/IPlacementObject.cs
using System.Collections;

namespace HC3;

/// <summary>
/// Describes a grouping for a placeable object. It's done in a way that it's more like a tree.
/// </summary>
/// <param name="Groups"></param>
public record struct Group( params string[] Groups ) : IEnumerable<string>
{
	public IEnumerator<string> GetEnumerator() => ((IEnumerable<string>)Groups).GetEnumerator();
	IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
	public string MainGroup => Groups.FirstOrDefault();
	public string SecondaryGroup => Groups.ElementAtOrDefault( 1 );
}

/// <summary>
/// Describes a placeable object, like a building, a decoration
/// </summary>
public interface IPlacementObject : IValid
{
	/// <inheritdoc cref="Sandbox.GameObject"/>
	GameObject GameObject { get; }

	/// <inheritdoc cref="GameObject.WorldPosition"/>
	Vector3 WorldPosition { get; set; }

	/// <inheritdoc cref="GameObject.WorldRotation"/>
	Rotation WorldRotation { get; set; }

	/// <summary>
	/// Has this item been placed - this seems useless to have in the interface?
	/// </summary>
	bool IsPlaced { get; set; }

	/// <summary>
	/// How much does this item cost to purchase?
	/// </summary>
	public int Cost { get; }

	/// <summary>
	/// A nice title for the object
	/// </summary>
	public string Title { get; }

	/// <summary>
	/// Optional prefab source
	/// </summary>
	public string PrefabSource { get; }

	/// <summary>
	/// A thumbnail image
	/// </summary>
	[Property, ImageAssetPath]
	public string Thumbnail { get; }

	/// <summary>
	/// A grouping for the item
	/// </summary>
	[Property, InlineEditor]
	public Group Group { get; }

	/// <summary>
	/// We can define tags on items to show up on the top
	/// </summary>
	/// <returns></returns>
	public IEnumerable<string> GetTags() => null;

	/// <summary>
	/// Sort order
	/// </summary>
	/// <returns></returns>
	int GetOrder() => 0;

	/// <summary>
	/// Accessor to grab tint component from the object's <see cref="GameObject"/>
	/// </summary>
	/// <returns></returns>
	public TintMaskComponent GetTintComponent()
	{
		return GameObject
			.GetComponentsInChildren<TintMaskComponent>()
			.FirstOrDefault();
	}

	public static IEnumerable<IPlacementObject> GetAllPrefabs()
	{
		return ResourceLibrary.GetAll<PrefabFile>()
			.Where( x => !string.IsNullOrEmpty( x.GetMetadata( "Type", "" ) ) )
			.Select( x => GameObject.GetPrefab( x.ResourcePath ) )
			.ToList()
			.Where( x => x.GetComponent<IPlacementObject>().IsValid() )
			.Select( x => x.GetComponent<IPlacementObject>() );
	}

	public static IEnumerable<IPlacementObject> GetAllPrefabs( string groupName )
	{
		return GetAllPrefabs()
			.Where( x => x.Group.MainGroup.Equals( groupName ) );
	}


	public static bool CanPurchase( IPlacementObject item )
	{
		// Can't afford it
		if ( ParkManager.Instance?.Money < item.Cost )
			return false;

		// Needs to be unlocked via goals
		if ( item is Building building )
		{
			var goal = Goal.GetRequiredForBuilding( building );
			if ( goal.IsValid() && !goal.IsUnlocked() )
				return false;
		}

		return true;
	}

	public static string GetPurchaseFailedReason( IPlacementObject item )
	{
		if ( item is Building building )
		{
			var goal = Goal.GetRequiredForBuilding( building );
			if ( goal.IsValid() && !goal.IsUnlocked() )
				return $"You need to complete the goal '{goal.GoalName}' to unlock {item.Title}";
		}

		if ( ParkManager.Instance?.Money < item.Cost )
			return $"You can't afford {item.Title} ({item.Cost} money)";

		return null;
	}
}