Editor/Porch/ArchPorchUi.cs
using System;
using System.Linq;
using Editor;
using Sandbox;

namespace Sunless.Architecture;

// One sheet, read by the Porch subtool's draft AND by Select, off the same record - which is the whole
// reason a placed porch can now be given a roof at all. The roof used to be a flag on the subtool, so it
// could only ever be answered while the mouse was still down.
public static class ArchPorchUi {
	// A tick that GATES the fields under it has to rebuild the sheet as well as commit, or the fields it
	// reveals never arrive and the ones it hides stay put.
	static Action Both( Action changed, Action refresh ) {
		return () => {
			changed?.Invoke();
			refresh?.Invoke();
		};
	}

	// Each form takes a Layout rather than a sidebar, so the tool can ask them one per step and the sheet can lay
	// them all out in a row.
	public static void Standing( Layout group, ArchPorchPart porch, Action refresh, Action changed ) {
		var both = Both( changed, refresh );

		using var grid = ArchIconGrid.In( group );

		grid.Pick( "Against the house — the shell is subtracted, so a drag over a corner wraps it", "porch_against", "home",
			porch.Standing == PorchStanding.Against, () => { porch.Standing = PorchStanding.Against; both(); } );

		grid.Pick( "Free-standing — the drag IS the deck, which is how one stands inside a room", "porch_free", "crop_free",
			porch.Standing == PorchStanding.Free, () => { porch.Standing = PorchStanding.Free; both(); } );
	}

	// Roofed or not is one two-state answer, so it is a pick rather than a lone tick - the pitch only means
	// anything under the roofed half of it.
	public static void Roof( Layout group, ArchPorchPart porch, Action refresh, Action changed ) {
		var both = Both( changed, refresh );

		using ( var grid = ArchIconGrid.In( group ) ) {
			grid.Pick( "Roofed — a section re-cut from where the legs stand, tucked under whatever eave hangs over it",
				"opt_porch_roof", "roofing", porch.Roofed, () => { porch.Roofed = true; both(); } );

			grid.Pick( "Open — a deck with nothing over it", "opt_porch_open", "deck",
				!porch.Roofed, () => { porch.Roofed = false; both(); } );
		}

		if ( !porch.Roofed ) {
			return;
		}

		group.Add( ArchPartUi.Number( "Pitch", porch.Pitch, 0f, value => porch.Pitch = value, changed ) );
	}

	public static void Include( Layout group, ArchPorchPart porch, Action changed ) {
		using var grid = ArchIconGrid.In( group );

		grid.Toggle( "Deck under it — off leaves a canopy over open ground", "opt_porch_deck", "layers", porch.Deck,
			value => { porch.Deck = value; changed?.Invoke(); } );

		grid.Toggle( "Exposed rafters instead of a lined ceiling", "opt_rafters", "reorder", porch.Rafters,
			value => { porch.Rafters = value; changed?.Invoke(); } );

		grid.Toggle( "Raking struts back to the wall", "opt_braces", "call_made", porch.Braces,
			value => { porch.Braces = value; changed?.Invoke(); } );

		grid.Toggle( "Railing on the open edges", "opt_railing", "fence", porch.Railings,
			value => { porch.Railings = value; changed?.Invoke(); } );

		grid.Toggle( "Door through the wall it fronts — the way in off the deck", "opt_porch_door", "door_front", porch.Door,
			value => { porch.Door = value; changed?.Invoke(); } );
	}

	public static void Frame( Layout group, ArchPorchPart porch, Action changed ) {
		using ( var grid = ArchIconGrid.In( group ) ) {
			grid.Toggle( "Plinth under the deck", "opt_plinth", "vertical_align_bottom", porch.Plinth,
				value => { porch.Plinth = value; changed?.Invoke(); } );

			grid.Toggle( "Posts on the open perimeter", "opt_posts", "view_column", porch.Posts,
				value => { porch.Posts = value; changed?.Invoke(); } );

			grid.Toggle( "Header beam over the posts", "opt_beam", "horizontal_rule", porch.Beam,
				value => { porch.Beam = value; changed?.Invoke(); } );
		}

		group.Add( ArchPartUi.Number( "Post spacing", porch.PostSpacing, 96f, value => porch.PostSpacing = value, changed ) );
	}

	public static void Placement( Layout group, ArchPorchPart porch, Action changed ) {
		group.Add( ArchPartUi.Number( "Deck drop", porch.DeckDrop, 24f,
			value => porch.GradeHeight = porch.BaseHeight - value, changed ) );

		group.Add( ArchPartUi.Number( "Head height", porch.HeadHeight, 100f, value => porch.HeadHeight = value, changed ) );
	}

	public static string StandingName( ArchPorchPart porch ) => porch.Standing == PorchStanding.Against ? "Against" : "Free-standing";

	public static string StandingGlyph( ArchPorchPart porch ) => porch.Standing == PorchStanding.Against ? "home" : "crop_free";

	public static void Build( ToolSidebarWidget panel, ArchPorchPart porch, Action refresh, Action changed ) {
		Standing( panel.AddGroup( "Standing" ), porch, refresh, changed );

		ArchSidebarSection.Show( panel, "Porch.placement", "Placement", group => Placement( group, porch, changed ) );
		ArchSidebarSection.Show( panel, "Porch.roof", "Roof", group => Roof( group, porch, refresh, changed ) );
		ArchSidebarSection.Show( panel, "Porch.include", "Include", group => Include( group, porch, changed ) );
	}

	public static void Sizes( ToolSidebarWidget panel, ArchPorchPart porch, Action changed ) {
		ArchSidebarSection.Disclosure( panel, "Porch.frame", "Frame", true, group => Frame( group, porch, changed ) );
	}

	// The placement draft has no plan to file a flight into, so the button only exists on a standing porch.
	// A flight is a REAL layer here - it gets a row, handles and a sheet - and the railing's gap is read back
	// off it, so adding a second way down needs nothing else said anywhere.
	public static void Steps( ToolSidebarWidget panel, ArchTool tool, ArchSelection picked, ArchPorchPart porch, Action refresh, Action changed ) {
		ArchSidebarSection.Show( panel, "Porch.steps", "Steps", group => {
			group.Add( ArchPartUi.Wrapped( porch.Stairs.Count == 0
				? "No way down. A flight added here becomes its own row — drag it to move it along the deck."
				: $"{porch.Stairs.Count} flight{(porch.Stairs.Count == 1 ? "" : "s")} off this deck, each its own row." ) );

			if ( porch.DeckDrop < 4f ) {
				group.Add( ArchPartUi.Wrapped( "The deck stands too close to grade for a flight." ) );

				return;
			}

			group.Add( new Button( "Add a flight", "stairs" ) {
				Clicked = () => {
					var shape = ArchPorchShape.Resolve( porch, picked.Room, picked.Building, tool.Kit );

					if ( ArchPorchSteps.Stand( tool.Plan, porch, shape, tool.Kit ) is null ) {
						Log.Info( "Architecture: that porch has no open edge left to bring a flight down through." );

						return;
					}

					changed?.Invoke();
					refresh?.Invoke();
				}
			} );
		} );
	}
}

public sealed class ArchPorchSheet : IArchSheet {
	public Type Payload => typeof( ArchPorchPart );

	public void Build( ToolSidebarWidget panel, ArchTool tool, ArchSelection picked, Action refresh, Action changed ) {
		if ( picked.Item is not ArchPorchPart porch ) {
			return;
		}

		ArchPorchUi.Build( panel, porch, refresh, changed );
		ArchPorchUi.Sizes( panel, porch, changed );
		ArchPorchUi.Steps( panel, tool, picked, porch, refresh, changed );
	}
}