Editor/Road/ArchRoadExtentProviders.cs
namespace Sunless.Architecture;

public sealed class ArchRoadExtentProvider : IArchExtentProvider {
	public ArchKind Kind => ArchKind.Road;

	public ArchExtent Measure( object payload, ArchExtentContext context ) {
		if ( payload is not ArchRoadPart road ) {
			return new ArchExtent();
		}

		var extent = new ArchExtent();
		var curve = road.Curve();
		var reach = road.Reach();
		var stand = road.KerbHeight + (road.Barriers == RoadSide.None ? 0f : road.Barrier.Height + road.Barrier.PostOverhang);
		var frames = ArchRoadGen.Frames( road, curve );

		foreach ( var frame in frames ) {
			extent.Add( frame.Side( -reach ) );
			extent.Add( frame.Side( reach ) );
			extent.Add( frame.Position - Vector3.Up * (road.Thickness + road.KerbHeight + road.Camber) );
			extent.Add( frame.Position + Vector3.Up * stand );
		}

		foreach ( var bridge in ArchLayerGate.Enabled( road.Bridges ) ) {
			extent.Add( new ArchBridgeExtentProvider().Measure( bridge, context ) );
		}

		foreach ( var tunnel in road.Tunnels ) {
			extent.Add( new ArchTunnelExtentProvider().Measure( tunnel, context ) );
		}

		return extent.Grown( 4f );
	}
}

public sealed class ArchBridgeExtentProvider : IArchExtentProvider {
	public ArchKind Kind => ArchKind.Bridge;

	public ArchExtent Measure( object payload, ArchExtentContext context ) {
		if ( payload is not ArchBridgePart bridge || context.Plan?.RoadCarrying( bridge ) is not { } road ) {
			return new ArchExtent();
		}

		var extent = new ArchExtent();
		var curve = road.Curve();
		var frames = ArchRoadGen.Frames( road, curve );
		var ground = new ArchGround( context.Tool?.Scene );
		var shape = ArchBridge.Resolve( road, bridge, context.Plan, curve, frames, context.Kit ?? new ArchKit(), ground );

		if ( !shape.IsUsable ) {
			return extent;
		}

		extent.Add( shape.Near.Frame.Position.WithZ( shape.Near.Ground ) );
		extent.Add( shape.Far.Frame.Position.WithZ( shape.Far.Ground ) );

		foreach ( var bent in shape.Bents ) {
			extent.Add( bent.Frame.Position.WithZ( bent.Ground ) );
		}

		return extent;
	}
}

public sealed class ArchTunnelExtentProvider : IArchExtentProvider {
	public ArchKind Kind => ArchKind.Tunnel;

	public ArchExtent Measure( object payload, ArchExtentContext context ) {
		if ( payload is not ArchTunnelPart tunnel || context.Plan?.RoadHolding( tunnel ) is not { } road ) {
			return new ArchExtent();
		}

		var extent = new ArchExtent();
		var curve = road.Curve();
		var kit = context.Kit ?? new ArchKit();
		var shape = ArchTunnel.Resolve( road, tunnel, curve, ArchRoadGen.Frames( road, curve ), kit,
			new ArchGround( context.Tool?.Scene ) );

		if ( !shape.IsUsable ) {
			return extent;
		}

		var reach = ArchTunnel.Reach( tunnel, shape.Section );

		foreach ( var frame in shape.Frames ) {
			extent.Add( frame.Side( -reach ).WithZ( frame.Position.z + shape.Section.Floor - tunnel.Invert ) );
			extent.Add( frame.Side( reach ).WithZ( frame.Position.z + shape.Section.Crown + tunnel.Lining ) );
		}

		foreach ( var mouth in new[] { shape.Near, shape.Far } ) {
			extent.Add( mouth.Frame.Side( -reach ).WithZ( mouth.Foot ) );
			extent.Add( mouth.Frame.Side( reach ).WithZ( mouth.Head ) );
		}

		return extent;
	}
}