Editor/Tool/Subtools/ArchBridgeSubtool.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;

namespace Sunless.Architecture;

[Title( "Bridge" ), Icon( "linear_scale" ), Group( "04" )]
public sealed class ArchBridgeSubtool( ArchTool owner ) : ArchSubtool( owner ) {
	protected override ArchKind? DraftKind => ArchKind.Bridge;

	public override ArchSurface[] Surfaces => new[]
	{
		ArchSurface.Bridge, ArchSurface.Pier, ArchSurface.Post, ArchSurface.Railing, ArchSurface.Road
	};

	ArchBridgePart draft = new();

	// Re-dresses the newest span in the plan; authoring anything else moves the counter.
	int placed;
	int stamp;

	protected override string Title() => "Span Bridge";

	protected override string Advice() => "Drag between two roads to join and span them, or along one to bridge it.";

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

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

	// Shown in the panel - an untold live edit reads as the tool acting alone.
	ArchBridgePart Placed => placed != 0 && Owner.Plan.NextId == stamp ? Owner.Plan.FindBridge( placed ) : null;

	ArchBridgePart Edited => Placed ?? draft;

	// A bridge IS its structure - a type replaces it whole, nothing per-drag to seed. Advancing the step is the
	// refresh, so this one never calls for a second.
	void Adopt( ArchBridgeType type ) {
		if ( Placed is { } span ) {
			ArchRoadUi.Retype( Owner, span, type );
			Owner.Touch( "Retype Bridge" );

			return;
		}

		draft = type.Spanning( 0f, 0f );
	}

	void Seeded() {
		if ( draft.Type.Length > 0 || ArchRoadCatalogs.Bridges().FirstOrDefault() is not { } first ) {
			return;
		}

		draft = first.Spanning( 0f, 0f );
	}

	protected override void OnDrag( Vector2 from, Vector2 to ) {
		Seeded();

		if ( !Reached( from, out var near, out _ ) ) {
			return;
		}

		if ( Reached( to, out var far, out _ ) && far != near ) {
			Join( near, far );

			return;
		}

		if ( Spanned( from, to, out var road, out var span ) ) {
			Add( road, span );
		}
	}

	// Bridging makes the two runs ONE; the near keeps name and section, the far is absorbed.
	void Join( ArchRoadPart near, ArchRoadPart far ) {
		var join = ArchJoin.Across( near, far, ArchBridge.ShortestSpan );

		if ( !join.IsUsable ) {
			Log.Info( $"Architecture: {join.Refused}." );

			return;
		}

		join.Apply( Owner.Plan, near, far );
		Add( near, join.Span );
	}

	void Add( ArchRoadPart road, ArchRoadSpan span ) {
		var bridge = Edited.Copy( span.From, span.To );

		bridge.Id = Owner.Plan.AllocateId();
		bridge.Name = $"Bridge{road.Bridges.Count + 1}";

		road.Bridges.Add( bridge );
		FinishDraft( bridge.Id );
		Owner.Commit( "Span Bridge" );

		placed = bridge.Id;
		stamp = Owner.Plan.NextId;

		Refresh();
	}

	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 ), "drag along a road" );

			return;
		}

		var reach = road.Reach();

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

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

	protected override void DrawPreview() {
		if ( !Reached( DragStart, out var near, out _ ) ) {
			base.DrawPreview();

			return;
		}

		if ( Reached( DragCurrent, out var far, out _ ) && far != near ) {
			Joining( near, far );

			return;
		}

		if ( !Spanned( DragStart, DragCurrent, out var road, out var span ) ) {
			base.DrawPreview();

			return;
		}

		// A sketch - Show would edit the plan from inside a preview.
		var alone = road.Copy();

		alone.Nodes = road.Nodes;

		Show( alone, Owner.Resolved( road.Id, road.Curve ), span, road.Name );
	}

	// The drag shows the joined run - the easing through the join is in neither centreline.
	void Joining( ArchRoadPart near, ArchRoadPart far ) {
		var join = ArchJoin.Across( near, far, ArchBridge.ShortestSpan );

		if ( !join.IsUsable ) {
			ArchGhost.Note( new Vector3( DragCurrent.x, DragCurrent.y, Owner.LevelHeight ), join.Refused );

			return;
		}

		var sketch = near.Copy();

		sketch.Nodes = join.Nodes;

		var curve = ArchCurve.Of( join.Nodes );

		Gizmo.Draw.Color = ArchGhost.Line;
		ArchGhost.Curve( curve );

		Show( sketch, curve, join.Span, $"{near.Name} + {far.Name}" );
	}

	void Show( ArchRoadPart road, ArchCurve curve, ArchRoadSpan span, string what ) {
		var bridge = Edited.Copy( span.From, span.To );

		// So the walk keeps a station on each end, as the generator's does.
		road.Bridges = new List<ArchBridgePart> { bridge };

		var shape = ArchBridge.Resolve( road, bridge, Owner.Plan, curve, ArchRoadGen.Frames( road, curve ), Owner.Kit,
			new ArchGround( Owner.Scene ) );

		ArchRoadPreviewDraw.Bridge( shape, false );

		if ( !shape.IsUsable ) {
			ArchGhost.Note( new Vector3( DragCurrent.x, DragCurrent.y, Owner.LevelHeight ),
				$"{span.Length / 12f:0} ft - too short to span" );

			return;
		}

		ArchGhost.Note( shape.Far.Frame.Position,
			$"{what}: {span.Length / 12f:0} ft, {shape.Bents.Count} piers, {shape.Deepest / 12f:0.#} ft over the ground" );
	}

	// Both ends on ONE road - the drag's own; a too-short span is refused outright.
	bool Spanned( Vector2 from, Vector2 to, out ArchRoadPart road, out ArchRoadSpan span ) {
		span = default;

		if ( !Reached( from, out road, out var start ) || !Owner.Resolved( road.Id, road.Curve ).Nearest( to, out var end, out _ ) ) {
			return false;
		}

		span = new ArchRoadSpan {
			From = MathF.Min( start.Distance, end.Distance ),
			To = MathF.Max( start.Distance, end.Distance )
		};

		return span.Length >= ArchBridge.ShortestSpan;
	}

	// The click must land on the road's own surface - a garden drag spans nothing.
	bool Reached( Vector2 point, out ArchRoadPart road, out ArchFrame frame ) {
		road = Owner.RoadAt( point, out frame );

		return road is not null;
	}

	// One list: the type first, because a bridge IS its structure and picking one replaces the lot; then the pier
	// and the ends, which are what the span reads as from the street; then the typed rows. Seeding is hoisted here
	// because a closed step never builds and the ghost that follows the cursor is drawn from what it seeds.
	protected override void BuildOptions( ToolSidebarWidget panel ) {
		Seeded();

		var part = Edited;
		var live = Placed;
		var type = ArchRoadCatalogs.Bridge( part.Type );
		var changed = live is null ? null : (Action)(() => Owner.Touch( $"Retune {live.Name}" ));

		if ( live is not null ) {
			panel.Layout.Add( ArchSidebarLayout.Advice( $"Editing {live.Name} live." ) );
		}

		using var flow = ArchWorkflow.In( panel, Scope( "flow" ), Refresh );

		flow.Step( "Type", type?.Title ?? "Hand-tuned", type?.Icon ?? "linear_scale", step =>
			ArchRoadUi.Types( step.Layout, part.Type, chosen => { Adopt( chosen ); step.Chose(); } ) );

		if ( ArchRoadUi.Wanted( type, ArchBridgeGroup.Pier ) ) {
			flow.Step( "Pier", part.Pier.ToString(), ArchRoadUi.PierGlyph( part.Pier ), step => {
				ArchRoadUi.Piers( step.Layout, part, step.Chose, changed );
				ArchRoadUi.PierSizes( step.Layout, part, changed );
			} );
		}

		if ( ArchRoadUi.Wanted( type, ArchBridgeGroup.Ends ) || ArchRoadUi.Wanted( type, ArchBridgeGroup.Parapet ) ) {
			flow.Step( "Ends", EndsName( part ), part.Abutments ? "foundation" : "fence", step => {
				ArchRoadUi.Ends( step.Layout, part, type, Refresh, changed );

				if ( part.Abutments && ArchRoadUi.Wanted( type, ArchBridgeGroup.Ends ) ) {
					ArchRoadUi.Abutments( step.Layout, part, changed );
				}
			} );
		}

		if ( part.Parapet && ArchRoadUi.Wanted( type, ArchBridgeGroup.Parapet ) ) {
			ArchBarrierUi.Steps( flow, part.Barrier, Refresh, changed );
		}

		if ( ArchRoadUi.Wanted( type, ArchBridgeGroup.Spans ) ) {
			flow.Step( "Spans", $"{part.Bents} piers", "view_column", step => ArchRoadUi.Spans( step.Layout, part, changed ) );
		}

		if ( ArchRoadUi.Wanted( type, ArchBridgeGroup.Deck ) ) {
			flow.Step( "Deck", $"{part.Slab:0.##} slab", "horizontal_rule", step => ArchRoadUi.Deck( step.Layout, part, changed ) );
		}
	}

	static string EndsName( ArchBridgePart bridge ) => ArchPartUi.Listed( (bridge.Abutments, "Abutments"), (bridge.Parapet, "Parapet") );
}