editor/AdvancedMappingTools/Procedural/SwitchbackGenerator.cs
namespace AdvancedMappingTools;

public enum SwitchbackStyle
{
	[Title( "Concrete Stairwell" )] Concrete,
	[Title( "Steel Fire Escape" )] FireEscape
}

public sealed class SwitchbackSettings : GeneratorSettings
{
	/// <summary>
	/// The length of one flight, set by the tool.
	/// </summary>
	public float Run { get; set; } = 160.0f;

	[Property, Group( "Stairs" ), WideMode]
	public SwitchbackStyle Style { get; set; } = SwitchbackStyle.Concrete;

	[Property, Group( "Stairs" ), Range( 1, 32, slider: false ), WideMode, Description( "Storeys the stairs climb." )]
	public int Storeys { get; set; } = 3;

	[Property, Group( "Stairs" ), Title( "Storey Height" ), Range( 48, 512, slider: false ), WideMode]
	public float StoreyHeight { get; set; } = 128.0f;

	[Property, Group( "Stairs" ), Title( "Flight Width" ), Range( 16, 256, slider: false ), WideMode]
	public float FlightWidth { get; set; } = 44.0f;

	[Property, Group( "Stairs" ), Title( "Gap Between Flights" ), Range( 0, 128, slider: false ), WideMode]
	public float Gap { get; set; } = 8.0f;

	[Property, Group( "Stairs" ), Title( "Landing Depth" ), Range( 16, 256, slider: false ), WideMode]
	public float LandingDepth { get; set; } = 48.0f;

	[Property, Group( "Stairs" ), Title( "Step Height" ), Range( 2, 16, slider: false ), WideMode]
	public float StepHeight { get; set; } = 7.5f;

	[Property, Group( "Details" ), Title( "Railings" ), WideMode]
	public bool Railings { get; set; } = true;

	[Property, ShowIf( nameof( Railings ), true ), Group( "Details" ), Title( "Railing Height" ), Range( 16, 64, slider: false ), WideMode]
	public float RailingHeight { get; set; } = 36.0f;

	[Property, Group( "Details" ), Title( "Broken Steps" ), Range( 0, 1 ), WideMode]
	public float BrokenSteps { get; set; } = 0.0f;

	[Property, Group( "Materials" ), Title( "Steps" ), ResourceType( "vmat" ), WideMode]
	public string StepMaterial { get; set; }

	[Property, Group( "Materials" ), Title( "Railings" ), ResourceType( "vmat" ), WideMode]
	public string TrimMaterial { get; set; }
}

/// <summary>
/// Stairs that double back at every landing, from a stairwell to a fire escape on an outside wall.
/// The first flight runs along +X from the origin, the next one back above it.
/// </summary>
public sealed class SwitchbackGenerator : IPartGenerator
{
	const string Category = "stair";

	public string Id => "switchback";
	public string Title => "Switchback Stairs";
	public Type SettingsType => typeof( SwitchbackSettings );

	public PartPlan Generate( GeneratorSettings settings, GenContext context )
	{
		var s = (SwitchbackSettings)settings;
		var root = context.Group( "root", "Switchback Stairs", "Building", Category );

		if ( s.Run < 32.0f ) return root;

		var steps = context.Material( s.StepMaterial );
		var trim = context.Material( s.TrimMaterial );
		var steel = s.Style == SwitchbackStyle.FireEscape;
		var half = s.StoreyHeight * 0.5f;
		var count = Math.Max( (int)MathF.Round( half / MathF.Max( s.StepHeight, 2.0f ) ), 2 );
		var offset = (s.FlightWidth + s.Gap) * 0.5f;
		var landingHeight = steel ? 2.0f : 8.0f;
		var total = s.FlightWidth + s.Gap * 0.5f;

		for ( var flight = 0; flight < s.Storeys * 2; flight++ )
		{
			var outward = flight % 2 == 0;
			var z = flight * half;
			var id = $"flight{flight}";
			var request = new StairRequest( id, $"Flight {flight + 1}", s.Run, s.FlightWidth, half, count, steel ? 1.5f : 4.0f,
				steel, s.Railings, s.RailingHeight, s.BrokenSteps, s.Variation, steps, trim );

			var plan = StairGenerator.Generate( request, context );

			root.Add( outward ? plan.At( new Vector3( 0.0f, -offset, z ) ) : plan.At( new Vector3( s.Run, offset, z ), 180.0f ) );

			var x0 = outward ? s.Run : -s.LandingDepth;
			var x1 = x0 + s.LandingDepth;
			var top = z + half;
			var name = $"landing{flight}";

			root.Add( context.Solid( name, $"Landing {flight + 1}", "Landing", Category, steel ? trim : steps,
				kit => kit.Box( new Vector3( x0, -total, top - landingHeight ), new Vector3( x1, total, top ) ) ) );

			if ( !s.Railings ) continue;

			var end = outward ? x1 - 2.0f : x0;

			root.Add( context.Solid( $"{name}_rail_end", "Landing Rail", "LandingRail", Category, trim,
				kit => kit.Box( new Vector3( end, -total, top + s.RailingHeight ), new Vector3( end + 2.0f, total, top + s.RailingHeight + 2.0f ) ) ) );

			foreach ( var (y, side) in new[] { (-total, "a"), (total - 2.0f, "b") } )
			{
				root.Add( context.Solid( $"{name}_post_{side}", "Landing Post", "LandingPost", Category, trim,
					kit => kit.Box( new Vector3( end, y, top ), new Vector3( end + 2.0f, y + 2.0f, top + s.RailingHeight ) ) ) );
			}
		}

		return root;
	}
}