Editor/Modules/ArchDiscovery.cs
namespace Sunless.Architecture;

// The ONE place the tool scans for a type, and it is only ever for a HOST HOOK - IArchHost and IArchDoorFitter,
// both answered by the game around the tool, which a library cannot name. Every other table writes out what stands
// in it: one assembly declares them all, and a scan holds a type handle for every hotloaded assembly the editor
// will never unload.
//
// A type that cannot be stood up used to take its whole table with it: TypeDescription.Create falls through to
// Activator.CreateInstance, which THROWS for a type with no parameterless constructor rather than handing back
// null. So nothing opens a type itself; what cannot stand is walked past and the games that can still answer.
public static class ArchDiscovery {
	public static IEnumerable<T> Enrolled<T>() where T : class {
		return EditorTypeLibrary.GetTypes<T>()
			.Where( Constructible )
			.Select( Made<T> )
			.Where( found => found is not null );
	}

	// For a table whose answer must not depend on which assembly the editor loaded first.
	public static IEnumerable<T> EnrolledByName<T>() where T : class {
		return EditorTypeLibrary.GetTypes<T>()
			.Where( Constructible )
			.OrderBy( type => type.ClassName )
			.Select( Made<T> )
			.Where( found => found is not null );
	}

	static bool Constructible( TypeDescription type ) {
		if ( type.IsAbstract || type.IsInterface || type.IsGenericType ) {
			return false;
		}

		return type.TargetType.IsValueType || type.TargetType.GetConstructor( Type.EmptyTypes ) is not null;
	}

	static T Made<T>( TypeDescription type ) where T : class {
		try {
			return type.Create<T>();
		} catch ( Exception fault ) {
			Log.Warning( $"{type.ClassName} declares {typeof( T ).Name} but threw while being constructed: {fault.Message}" );

			return null;
		}
	}
}