Editor/Layers/ArchLayerRules.cs

Decision logic for whether an architectural layer kind may be parented under another. Exposes CanParent overloads for root and non-root parents, returns an ArchLayerDecision with Allowed, HostOutput and Refusal text, and delegates manifest queries to ArchKindsAsked.

Reflection
using System.Collections.Generic;
using System.Linq;

namespace Sunless.Architecture;

// One answer to "may this child live under this parent?" - the typed hierarchy's capability matrix.
// Placement, reparenting and the tree's Add menu all ask here; a no never touches the plan.
public sealed class ArchLayerDecision
{
	public bool Allowed { get; init; }
	// The named output a child consumes from its parent - "Platform body and deck", say.
	public string HostOutput { get; init; } = "";
	// Why the pair was refused, readable as a tooltip or viewport note.
	public string Refusal { get; init; } = "";
}

public static class ArchLayerRules
{
	// The plan root: only a kind that roots at the plan stands at a domain head.
	public static ArchLayerDecision CanParent( ArchLayerRef parent, ArchKind childKind )
	{
		if ( parent.ItemId == 0 )
		{
			return CanParentRoot( childKind, "the plan root" );
		}

		return CanParent( parent.Kind, childKind );
	}

	public static ArchLayerDecision CanParent( ArchKind parentKind, ArchKind childKind )
	{
		if ( ValidChildren( parentKind ).Contains( childKind ) )
		{
			return new ArchLayerDecision { Allowed = true, HostOutput = HostOutput( parentKind ) };
		}

		return Refusal( childKind, ArchKindsAsked.Label( parentKind ) );
	}

	// The tree's Add menu: every kind that may sit under this parent. A kind with no manifest is not among them,
	// so the menu offers nothing rather than offering a dead row.
	public static IReadOnlyList<ArchKind> ValidChildren( ArchKind parentKind )
	{
		return ArchKindsAsked.ValidChildren( parentKind );
	}

	public static string HostOutput( ArchKind parentKind ) => ArchKindsAsked.HostOutput( parentKind );

	static ArchLayerDecision CanParentRoot( ArchKind childKind, string rootName )
	{
		return ArchKindsAsked.RootsAtPlan( childKind )
			? new ArchLayerDecision { Allowed = true, HostOutput = "Site plane" }
			: Refusal( childKind, rootName );
	}

	static ArchLayerDecision Refusal( ArchKind childKind, string parentName )
	{
		return new ArchLayerDecision
		{
			Allowed = false,
			Refusal = $"Cannot put {ArchKindsAsked.Label( childKind )} inside {parentName}."
		};
	}
}