Editor/Tool/Subtools/ArchCrossingSubtool.cs
using Editor;
using Sandbox;

namespace Sunless.Architecture;

[Title( "Crossing" ), Icon( "directions_walk" ), Group( "03" )]
public sealed class ArchCrossingSubtool( ArchTool owner ) : ArchSubtool( owner ) {
	public override ArchSurface[] Surfaces => new[] { ArchSurface.Pavement, ArchSurface.Kerb, ArchSurface.RoadLine };

	CrossingKind kind = CrossingKind.Driveway;
	float width = 132f;

	protected override bool UsesDrag => false;

	protected override string Title() => "Drop Crossing";

	protected override string Advice() => "Click a road where the kerb should drop.";

	protected override ArchKind? DraftKind => ArchKind.Crossing;

	public override void OnEnabled() {
		base.OnEnabled();

		ArchWorkflow.Restart( Scope( "flow" ) );
	}

	protected override void OnClick( Vector2 point ) {
		if ( Reached( point, out var road, out var frame ) ) {
			var crossing = new ArchRoadCrossing {
				Id = Owner.Plan.AllocateId(),
				Name = $"Crossing{road.Crossings.Count + 1}",
				Kind = kind,
				Distance = frame.Distance,
				Width = width,
				Side = Vector2.Dot( point - frame.Flat, ArchBarrierGen.Across( frame ) ) > 0f ? RoadSide.Right : RoadSide.Left
			};

			road.Crossings.Add( crossing );
			FinishDraft( crossing.Id );
			Owner.Commit( "Add Crossing" );
		}
	}

	protected override void DrawHover( Vector2 point ) {
		base.DrawHover( point );

		if ( !Reached( point, out var road, out var frame ) ) {
			ArchGhost.Note( new Vector3( point.x, point.y, Owner.LevelHeight ), "click a road" );
			return;
		}

		var reach = road.HalfWidth + road.PavementWidth;
		var half = frame.Along * (width * 0.5f);

		Gizmo.Draw.Color = ArchGhost.Accent;
		Gizmo.Draw.Line( frame.Side( -reach ), frame.Side( reach ) );

		ArchGhost.Note( frame.Position, $"{road.Name}  {kind}  {width / 12f:0.#} ft" );

		// A crossing's width is measured ALONG the road.
		Gizmo.Draw.Color = ArchGhost.Line;
		Gizmo.Draw.Line( frame.Side( -reach ) - half, frame.Side( reach ) - half );
		Gizmo.Draw.Line( frame.Side( -reach ) + half, frame.Side( reach ) + half );
	}

	// Verge not generous radius: the dropped kerb belongs to the pavement it breaks.
	bool Reached( Vector2 point, out ArchRoadPart road, out ArchFrame frame ) {
		road = Owner.RoadAt( point, candidate => candidate.HalfWidth + candidate.PavementWidth, out frame );

		return road is not null;
	}

	// Two rows: what kind of drop this is, and how much of the kerb it takes. The side is read off which side of
	// the centreline the click landed, so it is never asked.
	protected override void BuildOptions( ToolSidebarWidget panel ) {
		using var flow = ArchWorkflow.In( panel, Scope( "flow" ), Refresh );

		flow.Step( "Kind", kind.ToString(), ArchRoadUi.CrossingGlyph( kind ), step =>
			ArchRoadUi.Kinds( step.Layout, kind, value => { kind = value; step.Chose(); } ) );

		flow.Step( "Width", $"{width / 12f:0.#} ft", "straighten", step =>
			step.Layout.Add( ArchPartUi.Number( "Width", width, 132f, value => width = value ) ) );
	}
}