Editor/Stair/ArchStairEdges.cs
namespace Sunless.Architecture;

// Maps a step's four edges to along/across distances in the step's own frame.
public static class ArchStairEdges {
	public static void Head( ArchStairLane lane, Vector2 local ) {
		Lengthen( lane, Along( lane, local ) );
	}

	// Left flank = width change; right flank = origin shift.
	public static void Flank( ArchStairLane lane, Vector2 local, bool left ) {
		var across = Across( lane, local );

		if ( left ) {
			Widen( lane, across );

			return;
		}

		var width = lane.Width - across;

		Shift( lane, lane.Leftward * across );
		Widen( lane, width );
	}

	// The head pushed OUT by a distance rather than moved to a point, which is how a platform's far edge is grown.
	public static void Grow( ArchStairLane lane, float push ) {
		Lengthen( lane, lane.Length + push );
	}

	// Set outright, so a run or a depth typed on a sheet lands on the same number a dragged edge writes. The seat
	// never moves: a step grows out of the corner it was laid from, which is the corner the step below it meets.
	public static void Lengthen( ArchStairLane lane, float length ) {
		var reach = MathF.Max( ArchStairLanes.MinLane, length );

		switch ( lane.Walk ) {
			case StairWalk.Ahead:
				lane.AlongTo = lane.AlongFrom + reach;
				break;
			case StairWalk.Back:
				lane.AlongFrom = lane.AlongTo - reach;
				break;
			case StairWalk.Left:
				lane.AcrossTo = lane.AcrossFrom + reach;
				break;
			default:
				lane.AcrossFrom = lane.AcrossTo - reach;
				break;
		}
	}

	public static void Widen( ArchStairLane lane, float width ) {
		var reach = MathF.Max( ArchStairLanes.MinLane, width );

		switch ( lane.Walk ) {
			case StairWalk.Ahead:
				lane.AcrossTo = lane.AcrossFrom + reach;
				break;
			case StairWalk.Back:
				lane.AcrossFrom = lane.AcrossTo - reach;
				break;
			case StairWalk.Left:
				lane.AlongFrom = lane.AlongTo - reach;
				break;
			default:
				lane.AlongTo = lane.AlongFrom + reach;
				break;
		}
	}

	public static void Shift( ArchStairLane lane, Vector2 delta ) {
		lane.AlongFrom += delta.x;
		lane.AlongTo += delta.x;
		lane.AcrossFrom += delta.y;
		lane.AcrossTo += delta.y;
	}

	// A point in the core's numbers read as how far along the step's own travel it lies, and how far across it.
	public static float Along( ArchStairLane lane, Vector2 local ) => Vector2.Dot( local - lane.Seat, lane.Heading );

	public static float Across( ArchStairLane lane, Vector2 local ) => Vector2.Dot( local - lane.Seat, lane.Leftward );

	// Where each step STARTS in the climb, accumulated the way the resolve accumulates it - so the window's
	// elevation and the generator cannot disagree about how high a step stands.
	public static float[] Bases( ArchStairPart stair ) {
		var lanes = stair.Lanes;
		var climbs = ArchStairLanes.Climbs( stair.Core, lanes );
		var bases = new float[lanes.Count];
		var height = stair.BaseHeight;

		for ( var index = 0; index < lanes.Count; index++ ) {
			if ( !lanes[index].Climbs ) {
				height += climbs[index];
				bases[index] = height;

				continue;
			}

			bases[index] = height;
			height += climbs[index];
		}

		return bases;
	}
}