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

namespace Sunless.Architecture;

[Title( "Porch" ), Icon( "deck" ), Group( "13" )]
public sealed class ArchPorchSubtool( ArchTool owner ) : ArchSubtool( owner ) {
	protected override ArchKind? DraftKind => ArchKind.Porch;

	public override ArchSurface[] Surfaces => new[] { ArchSurface.Deck, ArchSurface.Railing, ArchSurface.Baseboard, ArchSurface.Roof, ArchSurface.Soffit };

	readonly ArchPorchPart draft = new() { GradeHeight = -24f };

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

	protected override string Advice() => "Drag the deck over the wall it hangs off.";

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

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

	// Resolved from the shape, not the drag, so the ghost matches the generator.
	protected override void DrawPreview() {
		var min = Min( DragStart, DragCurrent );
		var max = Max( DragStart, DragCurrent );
		var room = Owner.RoomAt( (min + max) * 0.5f, out var building );
		var deck = Owner.LevelHeight;
		var legs = ArchPorch.Preview( building, room, Owner.Kit, min, max, draft.Standing, out var standing, out var joining );

		if ( legs.Count == 0 ) {
			ArchGhost.Plate( min, max, deck, 8 );
			ArchGhost.Note( new Vector3( max.x, max.y, deck + 60f ), "porch — nowhere to stand a deck there" );
			return;
		}

		var sketch = Sketch( legs, standing );
		var shape = ArchPorchShape.Resolve( sketch, room, building, Owner.Kit );
		var head = shape.Head;
		var rail = deck + Owner.Kit.PorchRailHeight;

		foreach ( var leg in legs ) {
			if ( draft.Deck ) {
				ArchGhost.Volume( leg.Min, leg.Max, deck - draft.DeckDrop, deck );
			}

			ArchGhost.Plate( leg.Min, leg.Max, deck, 8 );
		}

		foreach ( var bay in ArchPorchGen.Bays( shape, sketch, Owner.Kit ) ) {
			if ( draft.Posts ) {
				ArchGhost.Post( bay.From.Point, Owner.Kit.PorchPostSize, deck, head );

				if ( bay.Last ) {
					ArchGhost.Post( bay.To.Point, Owner.Kit.PorchPostSize, deck, head );
				}
			}

			if ( draft.Railings && !bay.Gated ) {
				Gizmo.Draw.Line( bay.From.Raised.WithZ( rail ), bay.To.Raised.WithZ( rail ) );
			}
		}

		if ( draft.Roofed ) {
			ArchGhost.Ring( shape.Footprint, head + Owner.Kit.PorchBeamDepth );
		}

		ArchGhost.Note( new Vector3( max.x, max.y, head ), Note( legs, standing, joining ) );
	}

	static string Note( IReadOnlyList<ArchPorchLeg> legs, PorchStanding standing, ArchPorchPart joining ) {
		if ( joining is not null ) {
			return $"porch — joining {joining.Name}";
		}

		if ( standing == PorchStanding.Free ) {
			return "porch — free-standing, no wall to lean on";
		}

		return legs.Count > 1 ? $"porch — {legs.Count} legs, wrapping the corner" : "porch";
	}

	// Unattached to the plan, so the ghost resolves exactly what Attach will - the gates in its railing
	// included, which is why it carries the flight the placement is about to seed.
	ArchPorchPart Sketch( List<ArchPorchLeg> legs, PorchStanding standing ) {
		var deck = Owner.LevelHeight;

		var sketch = new ArchPorchPart {
			Legs = legs,
			Standing = standing,
			BaseHeight = deck,
			GradeHeight = deck - draft.DeckDrop,
			HeadHeight = MathF.Max( 60f, draft.HeadHeight ),
			PostSpacing = MathF.Max( 32f, Owner.Kit.PorchPostSize * 8f ),
			Deck = draft.Deck,
			Posts = draft.Posts,
			Rafters = draft.Rafters,
			Braces = draft.Braces,
			Pitch = draft.Pitch,
			Railings = draft.Railings,
			Steps = draft.Steps,
			Roofed = draft.Roofed
		};

		return sketch;
	}

	protected override void OnDrag( Vector2 from, Vector2 to ) {
		var min = Min( from, to );
		var max = Max( from, to );

		if ( (max - min).Length < 24f ) {
			return;
		}

		var room = Owner.RoomAt( (min + max) * 0.5f, out var building );
		var placement = ArchPorch.Attach( Owner.Plan, building, room, Owner.Kit, min, max, draft );

		if ( placement is null ) {
			Log.Info( "Architecture: no room under that drag for a porch to stand in." );
			return;
		}

		// A porch is a cluster of legs, not one part - the draft's work is done either way.
		FinishDraft();
		Owner.Commit( "Create Porch" );
	}

	// One list: what the deck leans on and whether anything covers it - both picks - and then the three typed rows
	// that can never answer. A porch is a cluster of legs rather than one part, so nothing here is ever pointed at
	// a selection: a standing porch is edited from its own sheet in Select.
	protected override void BuildOptions( ToolSidebarWidget panel ) {
		using var flow = ArchWorkflow.In( panel, Scope( "flow" ), Refresh );

		flow.Step( "Standing", ArchPorchUi.StandingName( draft ), ArchPorchUi.StandingGlyph( draft ), step =>
			ArchPorchUi.Standing( step.Layout, draft, step.Chose, null ) );

		flow.Step( "Roof", draft.Roofed ? "Roofed" : "Open", draft.Roofed ? "roofing" : "deck", step =>
			ArchPorchUi.Roof( step.Layout, draft, step.Chose, null ) );

		flow.Step( "Include", IncludeName, "checklist", step => ArchPorchUi.Include( step.Layout, draft, null ) );

		flow.Step( "Frame", FrameName, "view_column", step => ArchPorchUi.Frame( step.Layout, draft, null ) );

		flow.Step( "Placement", $"{draft.DeckDrop:0} drop", "height", step => ArchPorchUi.Placement( step.Layout, draft, null ) );
	}

	string IncludeName => ArchPartUi.Listed(
		(draft.Deck, "Deck"), (draft.Rafters, "Rafters"), (draft.Braces, "Braces"), (draft.Railings, "Railing"), (draft.Door, "Door") );

	string FrameName => ArchPartUi.Listed( (draft.Plinth, "Plinth"), (draft.Posts, "Posts"), (draft.Beam, "Beam") );
}