Editor/Stair/ArchStairWallRakes.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sunless.Architecture;

public sealed class ArchStairWallRakes : IArchWallRakes {
	public IReadOnlyList<ArchWallRake> Along(
		ArchWall wall,
		ArchRoom room,
		ArchBuilding building,
		ArchPlan plan,
		ArchKit kit,
		float half,
		float wallHeight ) {
		var rakes = new List<ArchWallRake>();

		if ( building is null ) {
			return rakes;
		}

		var kinds = ArchKinds.Load();
		var board = MathF.Max( 1f, kit.BaseboardHeight );

		foreach ( var candidate in building.Rooms ) {
			foreach ( var stair in plan.Filed( ArchKind.Stair, candidate, kinds ).OfType<ArchStairPart>() ) {
				var shape = ArchStairShape.Resolve( stair, candidate, kit, building.Rooms );

				Landings( rakes, wall, shape, room, kit, half, wallHeight );

				foreach ( var run in shape.Runs ) {
					if ( !Against( wall, run.Axes, run.Length, run.Width, kit, half, out var foot, out var sign ) ) {
						continue;
					}

					var slope = MathF.Max( 0.01f, run.Rise / MathF.Max( 1f, run.Going ) );
					var low = run.BaseHeight - room.BaseHeight;
					var start = MathF.Max( -run.Going, (-low - run.Rise - board) / slope );
					var finish = MathF.Min( run.Length - run.Going, (wallHeight - low) / slope );

					if ( finish - start < 1f ) {
						continue;
					}

					var lowLift = low + start * slope;
					var highLift = low + finish * slope;

					rakes.Add( sign > 0f
						? new ArchWallRake( foot + start, foot + finish, lowLift, highLift, run.Rise, wallHeight )
						: new ArchWallRake( foot - finish, foot - start, highLift, lowLift, run.Rise, wallHeight ) );
				}
			}
		}

		return rakes;
	}

	static void Landings( List<ArchWallRake> rakes, ArchWall wall, ArchStairShape shape, ArchRoom room, ArchKit kit, float half, float wallHeight ) {
		foreach ( var pad in shape.Pads ) {
			var lift = pad.Height - room.BaseHeight;

			if ( lift < 1f || lift > wallHeight ) {
				continue;
			}

			var length = pad.AlongTo - pad.AlongFrom;
			var width = pad.AcrossTo - pad.AcrossFrom;
			var lengthways = new ArchStairAxes { Origin = pad.Axes.Flat( pad.AlongFrom, pad.AcrossFrom ), Yaw = pad.Axes.Yaw };
			var crossways = new ArchStairAxes { Origin = pad.Axes.Flat( pad.AlongTo, pad.AcrossFrom ), Yaw = pad.Axes.Yaw + 90f };

			if ( Against( wall, lengthways, length, width, kit, half, out var foot, out var sign ) ) {
				rakes.Add( Level( foot, length, lift, sign, wallHeight ) );
				continue;
			}

			if ( Against( wall, crossways, width, length, kit, half, out foot, out sign ) ) {
				rakes.Add( Level( foot, width, lift, sign, wallHeight ) );
			}
		}
	}

	static ArchWallRake Level( float foot, float length, float lift, float sign, float wallHeight ) {
		return sign > 0f
			? new ArchWallRake( foot, foot + length, lift, lift, 0f, wallHeight )
			: new ArchWallRake( foot - length, foot, lift, lift, 0f, wallHeight );
	}

	static bool Against( ArchWall wall, ArchStairAxes axes, float length, float width, ArchKit kit, float half, out float foot, out float sign ) {
		foot = 0f;
		sign = 1f;

		var lean = Vector2.Dot( axes.Along, wall.Direction );

		if ( MathF.Abs( lean ) < 0.98f ) {
			return false;
		}

		foreach ( var edge in new[] { 0f, width } ) {
			var middle = axes.Flat( length * 0.5f, edge );
			var offset = Vector2.Dot( middle - wall.Start, wall.Normal );

			if ( offset < -half - 1f || offset > half + ArchProbe.Gap( kit ) ) {
				continue;
			}

			foot = Vector2.Dot( axes.Flat( 0f, edge ) - wall.Start, wall.Direction );
			sign = lean > 0f ? 1f : -1f;

			return true;
		}

		return false;
	}
}