Editor/Stair/ArchExteriorStairAffector.cs
using System;
using System.Linq;

namespace Sunless.Architecture;

// A flight hung on an elevation opens whatever its runs walk through, so it is an effect on a host it does
// not own and obeys the affector contract: the archways are re-cut from where the flight stands NOW on every
// commit, and the group it forms is the scope Breach is then allowed to reach.
//
// Known gap it inherits: Breach tests RUNS only. A landing that straddles a wall - which a switchback dragged
// past the corner of an elevation can do - is not opened, and is not tested either.
public static class ArchExteriorStairAffector {
	public static int Resolve( ArchPlan plan, ArchKit kit ) {
		var recut = 0;
		var kinds = ArchKinds.Load();

		foreach ( var building in plan.Buildings.ToList() ) {
			foreach ( var part in plan.Filed<ArchExteriorStairPart>( building, kinds ).ToList() ) {
				var shape = ArchExteriorStair.Resolve( part, building, kit );

				if ( !shape.IsUsable ) {
					continue;
				}

				// Any flight will do as the key: every leg of one switchback is filed under the routing's own id.
				ArchStairShape.Breach( plan, shape.Flights[0].Stair, shape.Walk(), kit, ArchLayerGroups.Reach( plan, part.Id, building.Id ) );
				Regroup( plan, building, part, kinds );

				recut++;
			}
		}

		return recut;
	}

	// The buildings the flight actually opened, gathered with it and with the one it hangs on. A flight that
	// walks nowhere but its own elevation is no relationship worth a folder, so it forms none.
	static void Regroup( ArchPlan plan, ArchBuilding home, ArchExteriorStairPart part, ArchKinds kinds ) {
		var reached = plan.Buildings.Where( host => host != home && Opened( plan, host, part, kinds ) ).ToList();

		if ( reached.Count == 0 ) {
			ArchLayerGroups.Leave( plan, part.Id );

			return;
		}

		var join = ArchLayerGroups.Holding( plan, part.Id )
			?? ArchLayerGroups.Create( plan, ArchLayerGroups.Named( plan ), ArchAssemblyKind.SharedCore, Array.Empty<int>() );

		// The affector is filed last, because it acts on what sits above it in the stack.
		var members = reached.Select( host => host.Id ).Prepend( home.Id ).Append( part.Id ).ToList();

		foreach ( var member in members ) {
			ArchLayerGroups.Leave( plan, member );
		}

		join.Children.Clear();
		join.Children.AddRange( members );
	}

	static bool Opened( ArchPlan plan, ArchBuilding host, ArchExteriorStairPart part, ArchKinds kinds ) {
		return host.Rooms
			.SelectMany( room => plan.Filed( ArchKind.Wall, room, kinds ) )
			.SelectMany( wall => plan.Filed( ArchKind.Opening, wall, kinds ) )
			.Any( opening => opening is ArchOpening archway && archway.OwnerId == part.Id );
	}
}