Editor/Layers/ArchKindManifest.cs
using System;
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

public interface IArchKindManifest {
	ArchKind Kind { get; }

	Type Payload { get; }

	string Slot { get; }

	IEnumerable<Type> Hosts { get; }

	ArchLayerDomain Domain { get; }

	string Label { get; }

	string Glyph { get; }

	string Subtool { get; }

	bool RootsAtPlan { get; }

	// False for kinds painted onto their host rather than independently placed
	bool Transforms { get; }

	string HostOutput { get; }

	IEnumerable<ArchKind> Children { get; }

	// The inverse of Children — contributed kinds name their parents since the host can't name them
	IEnumerable<ArchKind> Parents { get; }

	// The layer this part hangs under, read from the part. Zero = plan root.
	int Parent( object payload );

	int DirectRank { get; }

	int IndirectRank { get; }

	ArchLayerStage Stage( object payload );

	string NameOf( object payload, int id );

	string DisplayName( object payload );

	bool TryRename( object payload, string name );

	IEnumerable<object> Filed( ArchPlan plan, object host );

	System.Collections.IList Folder( ArchPlan plan, object host );

	bool File( object host, object payload );

	bool Unfile( ArchPlan plan, object payload );

	IReadOnlyList<List<Vector2>> Loops( object payload );

	float Height( object payload, float fallback );
}

// Answers nothing by default, so a manifest states only what its kind has an opinion about. A kind with no shape,
// no children and no name of its own is a handful of lines.
public abstract class ArchKindManifest : IArchKindManifest {
	public abstract ArchKind Kind { get; }

	public abstract Type Payload { get; }

	public virtual string Slot => "";

	public virtual IEnumerable<Type> Hosts => Array.Empty<Type>();

	public virtual ArchLayerDomain Domain => ArchLayerDomain.Buildings;

	public virtual string Label => Kind.Ident;

	public virtual string Glyph => "crop_square";

	public virtual string Subtool => "";

	public virtual bool RootsAtPlan => false;

	public virtual bool Transforms => true;

	public virtual string HostOutput => "a host output";

	public virtual IEnumerable<ArchKind> Children => Array.Empty<ArchKind>();

	public virtual IEnumerable<ArchKind> Parents => Array.Empty<ArchKind>();

	public virtual int Parent( object payload ) => 0;

	public virtual int DirectRank => 200;

	public virtual int IndirectRank => 999;

	public virtual ArchLayerStage Stage( object payload ) => ArchLayerStage.Shape;

	public virtual string NameOf( object payload, int id ) => $"{Label} {id}";

	public virtual string DisplayName( object payload ) => null;

	public virtual bool TryRename( object payload, string name ) => false;

	public virtual IEnumerable<object> Filed( ArchPlan plan, object host ) => Array.Empty<object>();

	public virtual System.Collections.IList Folder( ArchPlan plan, object host ) {
		var holder = host ?? plan;

		return string.IsNullOrEmpty( Slot ) || holder is null
			? null
			: holder.GetType().GetProperty( Slot )?.GetValue( holder ) as System.Collections.IList;
	}

	public virtual bool File( object host, object payload ) => false;

	public virtual bool Unfile( ArchPlan plan, object payload ) => false;

	public virtual IReadOnlyList<List<Vector2>> Loops( object payload ) => Array.Empty<List<Vector2>>();

	public virtual float Height( object payload, float fallback ) => fallback;
}

public interface IArchNamed {
	string Name { get; set; }
}

public abstract class ArchKindManifest<TPayload> : ArchKindManifest where TPayload : class {
	public override Type Payload => typeof( TPayload );

	public override string NameOf( object payload, int id ) {
		return payload is TPayload and IArchNamed named ? named.Name : base.NameOf( payload, id );
	}

	public override bool TryRename( object payload, string name ) {
		if ( string.IsNullOrWhiteSpace( name ) || payload is not TPayload || payload is not IArchNamed named ) {
			return false;
		}

		named.Name = name;

		return true;
	}

	public override IEnumerable<object> Filed( ArchPlan plan, object host ) {
		return Hosting( host ) && Folder( plan, host ) is { } folder ? folder.Cast<object>() : Array.Empty<object>();
	}

	public override bool File( object host, object payload ) {
		if ( payload is not TPayload || !Hosting( host ) || Folder( null, host ) is not { } folder ) {
			return false;
		}

		folder.Add( payload );

		return true;
	}

	public override bool Unfile( ArchPlan plan, object payload ) {
		if ( plan is null || payload is not TPayload ) {
			return false;
		}

		foreach ( var host in ArchPlanHosts.Of( plan, Hosts ) ) {
			if ( Folder( plan, host ) is { } folder && folder.Contains( payload ) ) {
				folder.Remove( payload );

				return true;
			}
		}

		return false;
	}

	bool Hosting( object host ) => host is not null && Hosts.Any( type => type.IsInstanceOfType( host ) );
}

public interface IArchReachesWide {
	IEnumerable<object> Wide( ArchPlan plan );
}

public interface IArchNormalizes {
	void Normalize( ArchPlan plan );
}