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

public readonly record struct ArchStairStation( Vector2 At, StairWalk Walk );

public static class ArchStairSteps {
	public static ArchStairPart Owner( ArchPlan plan, ArchStairLane lane ) {
		if ( plan is null || lane is null ) {
			return null;
		}

		return plan.Parts<ArchStairPart>().FirstOrDefault( stair => stair.Lanes.Contains( lane ) );
	}

	public static int IndexOf( ArchStairPart stair, ArchStairLane lane ) {
		return stair?.Lanes.IndexOf( lane ) ?? -1;
	}

	public static ArchRoom Home( ArchPlan plan, ArchStairPart stair ) {
		if ( plan is null || stair is null ) {
			return null;
		}

		var kinds = ArchKinds.Load();

		return plan.AllRooms().FirstOrDefault( room => plan.Filed<ArchStairPart>( room, kinds ).Contains( stair ) );
	}

	// Single-step stairs move the shaft rise directly; multi-step stairs pin the lane.
	public static void Lift( ArchStairPart stair, ArchStairLane lane, float climb ) {
		if ( stair.Lanes.Count == 1 && stair.Core is { } core ) {
			core.Rise = MathF.Max( 1f, climb );
			lane.Rise = 0f;

			return;
		}

		lane.Rise = MathF.Max( 0f, climb );
	}

	public static int Ordinal( ArchStairPart stair, ArchStairLane lane ) {
		var count = 0;

		foreach ( var step in stair.Lanes ) {
			if ( step.Climbs == lane.Climbs ) {
				count++;
			}

			if ( step == lane ) {
				return count;
			}
		}

		return count;
	}

	static void Stand( ArchStairLane lane, ArchStairLane onto, StairWalk walk, float run, float width ) {
		Lay( lane, Seated( onto, walk, width ), walk, run, width, onto.Bearing );
	}

	static Vector2 Seated( ArchStairLane onto, StairWalk walk, float width ) {
		var heading = onto.Heading;
		var hand = onto.Leftward;
		var head = onto.Seat + heading * onto.Length;

		return onto.Walk.Between( walk ) switch {
			StairTurn.Left => head + hand * onto.Width,
			StairTurn.Right => onto.Seat + heading * MathF.Max( 0f, onto.Length - width ),
			StairTurn.Back => head + hand * (onto.Width + width),
			_ => head
		};
	}

	static void Lay( ArchStairLane lane, Vector2 seat, StairWalk walk, float run, float width, float bearing ) {
		var reach = seat + walk.Heading() * MathF.Max( ArchStairLanes.MinLane, run )
			+ walk.Leftward() * MathF.Max( ArchStairLanes.MinLane, width );

		lane.Walk = walk;
		lane.Bearing = bearing;
		lane.AlongFrom = MathF.Min( seat.x, reach.x );
		lane.AlongTo = MathF.Max( seat.x, reach.x );
		lane.AcrossFrom = MathF.Min( seat.y, reach.y );
		lane.AcrossTo = MathF.Max( seat.y, reach.y );
	}

	public static ArchStairLane Add( ArchPlan plan, ArchStairPart stair, ArchStairLane after ) {
		var index = after is null ? stair.Lanes.Count - 1 : IndexOf( stair, after );

		if ( index < 0 || stair.Core is null ) {
			return null;
		}

		var below = stair.Lanes[index];
		var flight = new ArchStairLane { Id = plan.AllocateId(), Step = StairStep.Flight };
		var chain = ArchStairChain.Of( stair );

		Stand( flight, below, below.Walk, Run( stair, index ), below.Width );

		stair.Lanes.Insert( index + 1, flight );
		chain.Insert( index + 1 );

		Relay( stair, index + 2, chain );
		ArchStairLanes.Reseat( stair.Core, stair.Lanes );

		return flight;
	}

	static float Run( ArchStairPart stair, int index ) {
		for ( var step = index; step >= 0; step-- ) {
			if ( stair.Lanes[step].Climbs ) {
				return stair.Lanes[step].Length;
			}
		}

		return MathF.Max( ArchStairLanes.MinLane, stair.Core.Length );
	}

	public static bool Aim( ArchStairPart stair, ArchStairLane lane, StairTurn turn ) {
		var index = IndexOf( stair, lane );

		if ( index <= 0 || stair.Core is null ) {
			return false;
		}

		var below = stair.Lanes[index - 1];
		var chain = ArchStairChain.Of( stair );

		Stand( lane, below, below.Walk.Turned( turn ), lane.Length, lane.Width );

		Relay( stair, index + 1, chain );
		ArchStairLanes.Reseat( stair.Core, stair.Lanes );

		return true;
	}

	public static bool Segment( ArchPlan plan, ArchStairPart stair, ArchStairLane lane, int count ) {
		return Segment( plan, stair, lane, count, 0f );
	}

	public static bool Curve( ArchPlan plan, ArchStairPart stair, ArchStairLane lane, int count, float sweep ) {
		return count >= 2 && Segment( plan, stair, lane, count, sweep );
	}

	static bool Segment( ArchPlan plan, ArchStairPart stair, ArchStairLane lane, int count, float sweep ) {
		var index = IndexOf( stair, lane );

		if ( index < 0 || !lane.Climbs || count < 2 || lane.Length / count < ArchStairLanes.MinLane ) {
			return false;
		}

		var guards = lane.Guards.ToList();
		var chain = ArchStairChain.Of( stair );
		var run = lane.Length / count;
		var width = lane.Width;
		var walk = lane.Walk;
		var bearing = lane.Bearing;
		var pinned = lane.Rise;
		var turn = sweep / (count - 1);
		var cut = new List<ArchStairLane>();

		for ( var part = 0; part < count; part++ ) {
			var piece = part == 0 ? lane : new ArchStairLane { Id = plan.AllocateId(), Step = StairStep.Flight };

			if ( part == 0 ) {
				Lay( piece, lane.Seat, walk, run, width, bearing );
			} else {
				Stand( piece, cut[part - 1], walk, run, width );

				piece.Bearing = bearing + turn * part;
			}

			piece.Rise = pinned / count;
			piece.Guards = Slice( plan, guards, part, count );

			cut.Add( piece );
		}

		stair.Lanes.RemoveAt( index );
		stair.Lanes.InsertRange( index, cut );

		chain.Insert( index + 1, count - 1 );

		Relay( stair, index + count, chain );
		ArchStairLanes.Reseat( stair.Core, stair.Lanes );

		return true;
	}

	static List<ArchStairGuardPart> Slice( ArchPlan plan, IReadOnlyList<ArchStairGuardPart> guards, int part, int count ) {
		var from = part / (float)count;
		var to = (part + 1) / (float)count;
		var kept = new List<ArchStairGuardPart>();

		foreach ( var guard in guards ) {
			var start = MathF.Max( guard.Start, from );
			var end = MathF.Min( guard.End, to );

			if ( end - start < 0.01f ) {
				continue;
			}

			var slice = guard.Copy();

			slice.Id = plan.AllocateId();
			slice.From = (start - from) * count;
			slice.To = (end - from) * count;

			kept.Add( slice );
		}

		return kept;
	}

	// Captures each step's relative joint and bearing so edits below propagate correctly above.
	readonly struct ArchStairChain {
		public List<StairTurn> Joints { get; init; }
		public List<float> Bearings { get; init; }

		public static ArchStairChain Of( ArchStairPart stair ) {
			var chain = new ArchStairChain {
				Joints = new List<StairTurn> { StairTurn.None },
				Bearings = new List<float> { stair.Lanes.Count > 0 ? stair.Lanes[0].Bearing : 0f }
			};

			for ( var index = 1; index < stair.Lanes.Count; index++ ) {
				chain.Joints.Add( stair.Lanes[index - 1].Walk.Between( stair.Lanes[index].Walk ) );
				chain.Bearings.Add( stair.Lanes[index].Bearing - stair.Lanes[index - 1].Bearing );
			}

			return chain;
		}

		public void Insert( int index, int count = 1 ) {
			Joints.InsertRange( index, Enumerable.Repeat( StairTurn.None, count ) );
			Bearings.InsertRange( index, Enumerable.Repeat( 0f, count ) );
		}

		public float Bearing( ArchStairPart stair, int index ) {
			return index < 1 ? Bearings[0] : stair.Lanes[index - 1].Bearing + Bearings[index];
		}
	}

	static void Relay( ArchStairPart stair, int from, ArchStairChain chain ) {
		for ( var index = Math.Max( 1, from ); index < stair.Lanes.Count; index++ ) {
			var lane = stair.Lanes[index];
			var below = stair.Lanes[index - 1];

			Stand( lane, below, below.Walk.Turned( chain.Joints[index] ), lane.Length, lane.Width );

			lane.Bearing = chain.Bearing( stair, index );
		}
	}

	public static ArchStairLane AddLanding( ArchPlan plan, ArchStairPart stair, ArchStairLane after ) {
		var index = IndexOf( stair, after );

		if ( index < 0 ) {
			return null;
		}

		var depth = MathF.Min( MathF.Max( ArchStairLanes.MinLane, after.Width ), after.Length * 0.4f );

		if ( ArchStairWalk.Split( after, depth ) is not { } landing ) {
			return null;
		}

		landing.Id = plan.AllocateId();
		stair.Lanes.Insert( index + 1, landing );

		return landing;
	}

	public static void Move( ArchStairPart stair, ArchStairLane lane, Vector2 to ) {
		var index = IndexOf( stair, lane );

		if ( index < 0 ) {
			return;
		}

		var chain = ArchStairChain.Of( stair );

		ArchStairEdges.Shift( lane, to - lane.Frame.Flat( lane.Length * 0.5f, lane.Width * 0.5f ) );

		Relay( stair, index + 1, chain );
	}

	public static IEnumerable<ArchStairStation> Stations( ArchStairLane below, float width ) {
		var frame = below.Frame;
		var span = MathF.Max( ArchStairLanes.MinLane, width );

		foreach ( var station in Along( below.Width, span ) ) {
			yield return new ArchStairStation( frame.Flat( below.Length, station ), below.Walk );
			yield return new ArchStairStation( frame.Flat( 0f, station ), below.Walk.Turned( StairTurn.Back ) );
		}

		foreach ( var station in Along( below.Length, span ) ) {
			yield return new ArchStairStation( frame.Flat( station, below.Width ), below.Walk.Turned( StairTurn.Left ) );
			yield return new ArchStairStation( frame.Flat( station, 0f ), below.Walk.Turned( StairTurn.Right ) );
		}
	}

	static IEnumerable<float> Along( float side, float width ) {
		var count = Math.Clamp( (int)MathF.Floor( side / width ), 1, MostStations );

		for ( var station = 0; station < count; station++ ) {
			yield return side * (station + 0.5f) / count;
		}
	}

	public const int MostStations = 4;

	public static bool JoinTo( ArchStairPart stair, ArchStairLane lane, ArchStairStation station ) {
		var index = IndexOf( stair, lane );

		if ( index < 1 ) {
			return false;
		}

		var chain = ArchStairChain.Of( stair );
		var below = stair.Lanes[index - 1];
		var run = lane.Length;
		var width = lane.Width;

		var frame = new ArchStairAxes { Yaw = Cardinal( station.Walk ) + below.Bearing };

		Lay( lane, station.At - frame.Across * (width * 0.5f), station.Walk, run, width, below.Bearing );

		Relay( stair, index + 1, chain );

		return true;
	}

	static float Cardinal( StairWalk walk ) => walk switch {
		StairWalk.Back => 180f,
		StairWalk.Left => 90f,
		StairWalk.Right => -90f,
		_ => 0f
	};

	public static void Settle( ArchStairPart stair, ArchStairLane lane ) {
		var index = IndexOf( stair, lane );

		if ( index < 0 ) {
			return;
		}

		var chain = ArchStairChain.Of( stair );

		if ( index > 0 ) {
			Meet( stair.Lanes[index - 1], lane );
		}

		Relay( stair, index + 1, chain );
	}

	public static void Bend( ArchStairPart stair, ArchStairLane lane, float bearing ) {
		var chain = ArchStairChain.Of( stair );

		lane.Bearing = bearing;

		Relay( stair, IndexOf( stair, lane ) + 1, chain );
	}

	// Only extends toward the step above when they share the same walk direction.
	static void Meet( ArchStairLane below, ArchStairLane above ) {
		if ( below.Walk != above.Walk ) {
			return;
		}

		ArchStairEdges.Lengthen( below, ArchStairEdges.Along( below, above.Seat ) );
	}

	public static bool Remove( ArchStairPart stair, ArchStairLane lane ) {
		var index = IndexOf( stair, lane );

		if ( index < 0 || stair.Lanes.Count <= 1 ) {
			return false;
		}

		stair.Lanes.RemoveAt( index );

		if ( index > 0 ) {
			Absorb( stair.Lanes[index - 1], lane );
		}

		ArchStairLanes.Fit( stair.Core, stair.Lanes );

		return true;
	}

	static void Absorb( ArchStairLane below, ArchStairLane gone ) {
		switch ( below.Walk ) {
			case StairWalk.Ahead when Overlaps( below.AcrossFrom, below.AcrossTo, gone.AcrossFrom, gone.AcrossTo ):
				below.AlongTo = MathF.Max( below.AlongTo, gone.AlongTo );
				break;

			case StairWalk.Back when Overlaps( below.AcrossFrom, below.AcrossTo, gone.AcrossFrom, gone.AcrossTo ):
				below.AlongFrom = MathF.Min( below.AlongFrom, gone.AlongFrom );
				break;

			case StairWalk.Left when Overlaps( below.AlongFrom, below.AlongTo, gone.AlongFrom, gone.AlongTo ):
				below.AcrossTo = MathF.Max( below.AcrossTo, gone.AcrossTo );
				break;

			case StairWalk.Right when Overlaps( below.AlongFrom, below.AlongTo, gone.AlongFrom, gone.AlongTo ):
				below.AcrossFrom = MathF.Min( below.AcrossFrom, gone.AcrossFrom );
				break;
		}
	}

	static bool Overlaps( float from, float to, float otherFrom, float otherTo ) {
		return from < otherTo - 0.05f && otherFrom < to - 0.05f;
	}
}