Editor/Tool/Subtools/ArchSpanSubtool.cs
using System;
using Editor;
using Sandbox;

namespace Sunless.Architecture;

// A span crosses between two piers, so it belongs to the tool that stands them: this is the Pillars subtool's
// third gesture, composed the way ArchFixtureSubtool is composed by the bool tool, and it holds no shelf entry
// of its own. Keeping the form in its own file keeps the two-click bridge out of the placement tool it rides in.
[Title( "Spans" ), Icon( "bridge" ), Group( "09" )]
public sealed class ArchSpanSubtool( ArchTool owner ) : ArchSubtool( owner ) {
	protected override ArchKind? DraftKind => ArchKind.Span;

	public override ArchSurface[] Surfaces => new[] { ArchSurface.PillarCap };

	ArchSpanPart draft = ArchSpanMemo.Recall();

	ArchSpanEnd taken;

	public ArchSpanPart LastPlaced { get; private set; }

	public ArchKind? MergedDraftKind => DraftKind;

	public string MergedAdvice => Advice();

	public ArchRunState MergedRun => Run();

	public bool MergedAdjusts => Adjusts;

	public void MergedCancel() => CancelRun();

	public void MergedHover( Vector2 point ) => DrawHover( point );

	// The host does the draft bookkeeping and the commit, exactly as the bool tool does for a fixture: this
	// returns what it stood and nothing else, so there is one place a placement is finished.
	public ArchSpanPart MergedClick( Vector2 point ) {
		LastPlaced = null;

		OnClick( point );

		return LastPlaced;
	}

	protected override bool UsesDrag => false;

	// Only over a span. The selection outlives the tool that made it, so an unconditional yes put the LAST
	// pillar's box dragger on screen the moment this tool was picked - and the gizmo takes the press first, so
	// every click meant for a pier went into a widget belonging to something else.
	protected override bool Adjusts => Owner.Picked?.Item is ArchSpanPart;

	protected override string Title() => "Spans";

	protected override string Advice() => taken is null
		? "Click a column or a wall to take one end. The nearest column within reach wins, so an eyeballed click still lands on the pier."
		: "Click the far column or wall and it bridges them. An end that lands on nothing stays where it was dropped.";

	protected override ArchRunState Run() => new( taken is not null, "one end taken" );

	protected override void CancelRun() => taken = null;

	protected override void DrawHover( Vector2 point ) {
		var room = Owner.RoomOnLevel( point );

		if ( room is null ) {
			base.DrawHover( point );

			return;
		}

		var landing = ArchSpanShape.Anchored( Owner.Plan, room, point );

		Pier( room, landing, false );

		if ( taken is null ) {
			return;
		}

		Pier( room, taken, true );
		Reaching( room, taken, landing );
	}

	protected override void OnClick( Vector2 point ) {
		var room = Owner.RoomOnLevel( point );

		if ( room is null ) {
			return;
		}

		var landing = ArchSpanShape.Anchored( Owner.Plan, room, point );

		if ( taken is null ) {
			taken = landing;

			return;
		}

		Bridge( room, taken, landing );
	}

	void Bridge( ArchRoom room, ArchSpanEnd from, ArchSpanEnd to ) {
		if ( Standing( room, from, to ) is not { } span || !ArchSpanShape.Resolve( Owner.Plan, Owner.Kit, room, span, out _ ) ) {
			Log.Info( "Architecture: nothing to bridge there — a span needs two ends apart, each under a pier tall enough to spring off." );

			return;
		}

		span.Id = Owner.Plan.AllocateId();
		span.Name = $"Span{room.PierSpans.Count + 1}";

		room.PierSpans.Add( span );
		Owner.Picked = new ArchSelection { Item = span, Room = room };

		LastPlaced = span;
		taken = null;
	}

	// A ring on the pier's HEAD and a stem down to the floor: where the end will spring from and which column it
	// grabbed, drawn as a mark on something rather than as a post, which is a pillar ghost by another name.
	void Pier( ArchRoom room, ArchSpanEnd end, bool held ) {
		var pier = ArchSpanShape.Pier( Owner.Plan, Owner.Kit, room, end );
		var lift = Lift( room );
		var head = new Vector3( pier.At.x, pier.At.y, pier.Head + lift );

		Gizmo.Draw.LineThickness = held ? 4f : 3f;
		Gizmo.Draw.Color = held ? ArchGhost.Accent : ArchGhost.Line;
		Gizmo.Draw.LineCircle( head, Vector3.Up, held ? 14f : 10f );
		Gizmo.Draw.Line( head, head.WithZ( room.BaseHeight + lift ) );
		Gizmo.Draw.LineThickness = 2f;

		if ( !pier.Standing ) {
			ArchGhost.Cursor( pier.At, room.BaseHeight + lift, ArchSpanShape.Grab );
		}

		Gizmo.Draw.Color = ArchGhost.Line;
	}

	// The plan is authored flat and the building is poured onto its grade, so a ghost drawn at plan height stands
	// a foundation below the span it is previewing.
	float Lift( ArchRoom room ) {
		var host = Owner.Plan.OwnerOf( room ) ?? Owner.ActiveBuilding();

		return host is null ? 0f : ArchAsks.Lift( Owner.Plan, host, Owner.Kit );
	}

	void Reaching( ArchRoom room, ArchSpanEnd from, ArchSpanEnd to ) {
		var lift = Lift( room );

		if ( Standing( room, from, to ) is not { } span
			|| !ArchSpanShape.Resolve( Owner.Plan, Owner.Kit, room, span, out var run ) ) {
			Gizmo.Draw.Line(
				new Vector3( from.At.x, from.At.y, room.BaseHeight + lift ),
				new Vector3( to.At.x, to.At.y, room.BaseHeight + lift ) );

			return;
		}

		var start = new Vector3( run.From.x, run.From.y, run.Springing + lift );
		var end = new Vector3( run.To.x, run.To.y, run.Springing + lift );

		var top = run.Top + lift;

		Gizmo.Draw.LineThickness = 4f;
		Gizmo.Draw.Color = ArchGhost.Accent;

		Gizmo.Draw.Line( start, end );
		Gizmo.Draw.Line( start.WithZ( top ), end.WithZ( top ) );
		Gizmo.Draw.Line( start, start.WithZ( top ) );
		Gizmo.Draw.Line( end, end.WithZ( top ) );

		Gizmo.Draw.LineThickness = 2f;
		Gizmo.Draw.Color = ArchGhost.Line;

		ArchGhost.Note( Vector3.Lerp( start.WithZ( top ), end.WithZ( top ), 0.5f ),
			$"{span.Form} — {(run.To - run.From).Length:0} across, {run.Top - run.Springing:0} deep, springs at {run.Springing - room.BaseHeight:0}" );
	}

	// ONE resolution the ghost and the placed part both read, so what the gesture showed is what the mesh comes out as.
	ArchSpanPart Standing( ArchRoom room, ArchSpanEnd from, ArchSpanEnd to ) {
		if ( room is null ) {
			return null;
		}

		var span = draft.Dressing();

		span.Level = room.Floor;
		span.From = from;
		span.To = to;

		return span;
	}

	// The host lays the rows: a bridge is the Pillars tool's third gesture, so its two questions belong in that
	// tool's one sequence rather than in a second list numbered from 1 underneath it.
	public void MergedSteps( ArchWorkflow flow ) {
		var span = Edited;
		var changed = Changed( span );

		flow.Step( "Form", span.Form.ToString(), ArchSpanUi.Glyph( span.Form ), step =>
			ArchSpanUi.Form( step.Layout, span, step.Chose, changed ) );

		flow.Step( "Section", ArchSpanUi.Measured( span ), "straighten", step =>
			ArchSpanUi.Section( step.Layout, span, changed ) );
	}

	// The picked span if there is one, else the seed for the next bridge - the same two steps either way, which is
	// what keeps a tuned span and the next one drawn from agreeing.
	public ArchSpanPart Edited => Editing() ?? draft;

	public string PickedName => Editing()?.Name;

	Action Changed( ArchSpanPart span ) {
		return Editing() is null
			? () => ArchSpanMemo.Remember( draft )
			: () => { Seed( span ); Owner.Touch( "Edit Span" ); };
	}

	// Tuning the span that was just placed IS the author saying how the next one should look - the panel points at
	// the selection, so without this every adjustment was spent on one part and the seed never moved.
	void Seed( ArchSpanPart span ) {
		draft = span.Dressing();

		ArchSpanMemo.Remember( draft );
	}

	ArchSpanPart Editing() => Owner.Picked?.Item as ArchSpanPart;
}