Editor/Data/ArchCatalogs.cs
namespace Sunless.Architecture;

// Which shelf a catalog stands on. A role rather than an interface per catalog, because the catalogs differ only in
// what they hold - a module brings its own with new ArchShelf( role ), and the reservation only stops two of them
// meaning the same thing.
public readonly record struct ArchShelf( string Role ) {
	public static readonly ArchShelf Openings = new( "openings" );
	public static readonly ArchShelf Walls = new( "walls" );
	public static readonly ArchShelf Pillars = new( "pillars" );
	public static readonly ArchShelf RoadLines = new( "roadlines" );
	public static readonly ArchShelf Profiles = new( "profiles" );
	public static readonly ArchShelf Cornices = new( "cornices" );
	public static readonly ArchShelf Cabinets = new( "cabinets" );
	public static readonly ArchShelf Types = new( "types" );

	public override string ToString() => Role;
}

// One catalog of authored types, owned by the module whose subtool authors them. The TYPE itself stays in core: a
// porch fits an opening preset it never authored, so a preset is shared geometry by the same rule a payload is,
// while the catalog around it belongs to one module alone.
public interface IArchCatalog {
	ArchShelf Shelf { get; }

	string Named( object entry );

	// What the module brings with it; an authored entry of the same name wins, so a tuned preset survives upgrades.
	IEnumerable<object> Shipped();

	// What stands on disk beside it, which tops what ships.
	IEnumerable<object> Authored();

	bool Save( object entry );
}

public sealed class ArchCatalogs : ArchTable<ArchCatalogs, ArchShelf, IArchCatalog> {
	protected override IEnumerable<IArchCatalog> Standing() {
		yield return new ArchArchetypeCatalog();
		yield return new ArchProfileCatalog();
		yield return new ArchCorniceCatalog();
		yield return new ArchCabinetCatalog();
		yield return new ArchOpeningCatalog();
		yield return new ArchWallCatalog();
		yield return new ArchPillarCatalog();
		yield return new ArchRoadLineCatalog();
	}

	protected override ArchShelf KeyOf( IArchCatalog catalog ) => catalog.Shelf;

	protected override bool Accepts( IArchCatalog catalog ) => !string.IsNullOrWhiteSpace( catalog.Shelf.Role );

	protected override string Collision( IArchCatalog standing, IArchCatalog catalog ) {
		return $"{catalog.Shelf} is claimed by both {standing.GetType().Name} and {catalog.GetType().Name}";
	}

	// A shelf no catalog claims stands empty rather than erroring.
	public IArchCatalog On( ArchShelf shelf ) => Held( shelf );

	public List<T> Ships<T>( ArchShelf shelf ) where T : class {
		return On( shelf ) is { } catalog ? catalog.Shipped().OfType<T>().ToList() : new List<T>();
	}

	// For a catalog whose authored folder IS the offering - a picker that browses what an author has on disk.
	public List<T> Authors<T>( ArchShelf shelf ) where T : class {
		return On( shelf ) is { } catalog ? catalog.Authored().OfType<T>().ToList() : new List<T>();
	}

	// What ships never displaces what is already held, and what is authored on disk always does - so an upgrade
	// brings new presets in beside the tuned ones and a written file is the last word on its own name.
	public void Stock<T>( ArchShelf shelf, List<T> saved ) where T : class {
		if ( saved is null || On( shelf ) is not { } catalog ) {
			return;
		}

		foreach ( var entry in catalog.Shipped().OfType<T>() ) {
			if ( !saved.Any( existing => Same( catalog.Named( existing ), catalog.Named( entry ) ) ) ) {
				saved.Add( entry );
			}
		}

		foreach ( var entry in catalog.Authored().OfType<T>() ) {
			var index = saved.FindIndex( existing => Same( catalog.Named( existing ), catalog.Named( entry ) ) );

			if ( index >= 0 ) {
				saved[index] = entry;
			} else {
				saved.Add( entry );
			}
		}
	}

	public bool Save( ArchShelf shelf, object entry ) => On( shelf ) is { } catalog && catalog.Save( entry );

	static bool Same( string first, string second ) => string.Equals( first, second, StringComparison.OrdinalIgnoreCase );
}

// For a one-off caller holding no table of its own. Reaching through it in a loop loads the shelf every time.
public static class ArchShelved {
	public static List<T> Ships<T>( ArchShelf shelf ) where T : class => ArchCatalogs.Load().Ships<T>( shelf );

	public static List<T> Authors<T>( ArchShelf shelf ) where T : class => ArchCatalogs.Load().Authors<T>( shelf );

	public static bool Save( ArchShelf shelf, object entry ) => ArchCatalogs.Load().Save( shelf, entry );
}