Editor/Roof/ArchRoofUi.cs
using System;
using System.Linq;
using Editor;
using Sandbox;

namespace Sunless.Architecture;

// ArchBuild.Roof derives shape and trims from the kit - placement controls would be overwritten.
public static class ArchRoofUi {
	// The tool is passed only where a roof is really STANDING - a draft has nothing to convert, so the sidebar that
	// seeds the next placement is offered no action that edits one.
	public static void Build( ToolSidebarWidget panel, ArchRoofPart roof, ArchKit kit, Action refresh, Action changed, ArchTool tool = null ) {
		ArchSidebarSection.Show( panel, "Roof.style", "Roof style", group => {
			using var grid = ArchIconGrid.In( group );

			foreach ( var value in Enum.GetValues<RoofStyle>() ) {
				var captured = value;

				grid.Pick( captured.ToString(), ArchIcons.RoofStyleSlug( captured ), ArchIcons.RoofStyleGlyph( captured ),
					roof.Style == captured, () => { roof.Style = captured; refresh?.Invoke(); changed?.Invoke(); } );
			}
		} );

		ArchSidebarSection.Show( panel, "Roof.edges", "Edges", group => {
			using var edges = ArchIconGrid.In( group );

			edges.Toggle( "Parapet wall cap", "opt_parapet", "crop_din", roof.Parapet,
				value => { roof.Parapet = value; refresh?.Invoke(); changed?.Invoke(); } );

			// A parapet stands where the gutter would hang, so the deck drains inside it and ArchBuild.Roof
			// refuses gutters outright - offering the toggle would be offering nothing.
			if ( !roof.Parapet ) {
				edges.Toggle( "Gutters", "opt_gutters", "water_damage", roof.Gutters,
					value => { roof.Gutters = value; changed?.Invoke(); } );
			}

			edges.Toggle( "Guardrail round the roof edge", "opt_guardrail", "fence", roof.Guardrail,
				value => { roof.Guardrail = value; refresh?.Invoke(); changed?.Invoke(); } );
		} );

		Cornice( panel, roof, kit, changed );
		Parapet( panel, roof, refresh, tool );
	}

	// ONE choice, and the stack behind it is the kit's - the same shelf a wall head crowns itself from, so a
	// parapet stood as walls afterwards keeps wearing what the ring wore. It stands as its own section whether or
	// not a cornice is on, because the roof/wall corner is what an author comes to this sheet to dress.
	static void Cornice( ToolSidebarWidget panel, ArchRoofPart roof, ArchKit kit, Action changed ) {
		if ( kit is null ) {
			return;
		}

		ArchSidebarSection.Show( panel, "Roof.cornice", "Cornice", group => {
			group.Add( ArchCorniceUi.Picker( panel, kit, "roof.cornices", () => roof.Cornice,
				name => { roof.Cornice = name; changed?.Invoke(); } ) );
		} );
	}

	// The ring is ONE height all the way round; standing it as walls is how it comes to vary. Each edge becomes a
	// real wall from there - raise it, drop it, dress it in brick, put a gate through it, delete one - and the
	// uniform ring switches itself off in the same breath so the deck never carries both.
	static void Parapet( ToolSidebarWidget panel, ArchRoofPart roof, Action refresh, ArchTool tool ) {
		if ( tool is null ) {
			return;
		}

		ArchSidebarSection.Show( panel, "Roof.parapet", "Parapet", group => {
			if ( roof.Walls.Count > 0 ) {
				group.Add( ArchPartUi.Wrapped( $"{roof.Walls.Count} wall{(roof.Walls.Count == 1 ? "" : "s")} standing on this deck."
					+ " Pick one in Plan Layers to change its height or dress it, or draw another with the Wall tool." ) );

				return;
			}

			group.Add( new Button( "Stand the parapet as walls", "fence" ) {
				ToolTip = "One wall per edge of the deck, so each can be raised, dressed or removed on its own",
				Clicked = () => {
					var stood = ArchRoofWalls.StandParapet( tool.Plan, roof, tool.Kit, ArchWallPreset.Copy( tool.Kit.Walls.FirstOrDefault() ) );

					if ( stood.Count == 0 ) {
						Log.Info( $"Architecture: {roof.Name} has no outline to run a parapet round." );

						return;
					}

					tool.Commit( $"Stand parapet on {roof.Name}" );
					refresh?.Invoke();
				}
			} );
		} );
	}

	// The same form the Roof subtool drafts a light or a plant item with and the Select subtool edits a placed one
	// through. Two numbers for all six forms: what they MEAN and which of them a form even has is
	// ArchRoofLightKinds' answer, so a chimney is never offered a pane width it would ignore. The footprint is
	// never here - it is dragged on the deck.
	public static void Light( ToolSidebarWidget panel, ArchRoofLightPart light, ArchKit kit, Action refresh, Action changed ) {
		var form = light.Form;

		ArchSidebarSection.Show( panel, "Roof.light", form.Label(), group => {
			group.Add( ArchPartUi.Number( form.RiseLabel(), light.Curb, form.RiseStock( kit ), value => light.Curb = value, changed ) );
			group.Add( ArchPartUi.Number( form.ReachLabel(), light.CurbWidth, form.ReachStock( kit ), value => light.CurbWidth = value, changed ) );

			if ( !form.Glazes() ) {
				return;
			}

			group.Add( ArchPartUi.Check( "Glazed", light.Glazed, value => { light.Glazed = value; refresh?.Invoke(); changed?.Invoke(); } ) );

			if ( light.Glazed ) {
				group.Add( ArchPartUi.Number( "Widest pane", light.PaneSpan, form.PaneStock(), value => light.PaneSpan = value, changed ) );
			}
		} );
	}

	// The one answer to whether a style has a ridge at all, so the creation sidebar and the property sheet
	// cannot disagree about whether to offer the row.
	// Auto lays the ridge down the longer side - only meaningful at placement.
	public static void Ridge( ToolSidebarWidget panel, ArchRoofPart roof, Action changed ) {
		if ( !ArchRoofPlane.Ridged( roof.Style ) ) {
			return;
		}

		ArchSidebarSection.Show( panel, "Roof.ridge", "Ridge", group => {
			using var grid = ArchIconGrid.In( group );

			Direction( grid, RidgeRun.AlongX, roof.RidgeAlongX, () => { roof.RidgeAlongX = true; changed?.Invoke(); } );
			Direction( grid, RidgeRun.AlongY, !roof.RidgeAlongX, () => { roof.RidgeAlongX = false; changed?.Invoke(); } );

			grid.Toggle( "Shed the other way", "opt_reversed", "swap_vert", roof.Reversed,
				value => { roof.Reversed = value; changed?.Invoke(); } );
		} );
	}

	// The shape is what a standing roof is judged on, so it opens; the boards and the lining under it are a
	// second pass over one already standing and fold away.
	public static void Details( ToolSidebarWidget panel, ArchRoofPart roof, Action changed ) {
		ArchSidebarSection.Show( panel, "Roof.shape", "Shape", shape => {
			shape.Add( ArchPartUi.Number( "Eave height", roof.BaseHeight, 128f, value => roof.BaseHeight = value, changed ) );

			// A flat deck falls nowhere, so the row would be a number that decides nothing. Drag an edge down
			// instead and the section becomes a shed carrying the pitch that drop asked for.
			if ( roof.Style != RoofStyle.Flat ) {
				shape.Add( ArchPartUi.Number( "Pitch", roof.Pitch, 30f, value => roof.Pitch = value, changed ) );
			}

			shape.Add( ArchPartUi.Number( "Overhang", roof.Overhang, 16f, value => roof.Overhang = value, changed ) );
			shape.Add( ArchPartUi.Number( roof.Style == RoofStyle.Flat ? "Slab thickness" : "Thickness", roof.Thickness, 6f, value => roof.Thickness = MathF.Max( 1f, value ), changed ) );

			if ( roof.Style == RoofStyle.Sawtooth ) {
				shape.Add( ArchPartUi.Integer( "Bays", roof.SawtoothBays, 4, value => roof.SawtoothBays = value, changed ) );
			}

			if ( roof.Parapet ) {
				shape.Add( ArchPartUi.Number( "Parapet height", roof.ParapetHeight, 18f, value => roof.ParapetHeight = value, changed ) );
			}

			if ( roof.Guardrail ) {
				shape.Add( ArchPartUi.Number( "Guardrail height", roof.GuardHeight, 42f, value => roof.GuardHeight = value, changed ) );

				ArchBarrierUi.GuardStyles( shape, roof.GuardStyle, style => {
					roof.GuardStyle = style;
					changed?.Invoke();
				} );
			}

			if ( roof.Frame ) {
				shape.Add( ArchPartUi.Number( "Rafter spacing", roof.FrameSpacing, 0f, value => roof.FrameSpacing = value, changed ) );
			}
		} );

		ArchSidebarSection.Disclosure( panel, "Roof.trims", "Trims", true, group => {
			using var grid = ArchIconGrid.In( group );

			grid.Toggle( "Exposed rafters and purlins under the deck", "opt_roof_frame", "reorder", roof.Frame,
				value => { roof.Frame = value; changed?.Invoke(); } );

			grid.Toggle( "Fascia along the eaves", "opt_fascia", "border_bottom", roof.Fascia,
				value => { roof.Fascia = value; changed?.Invoke(); } );

			grid.Toggle( "Boxed soffit under the overhang", "opt_soffit", "border_outer", roof.Soffit,
				value => { roof.Soffit = value; changed?.Invoke(); } );

			grid.Toggle( "Capping over the ridge and hips", "opt_ridge_cap", "roofing", roof.RidgeCap,
				value => { roof.RidgeCap = value; changed?.Invoke(); } );

			grid.Toggle( "End walls closing the roof volume", "opt_end_walls", "square", roof.EndWalls,
				value => { roof.EndWalls = value; changed?.Invoke(); } );
		} );

		ArchSidebarSection.Disclosure( panel, "Roof.inside", "Inside", true, group => {
			using var inside = ArchIconGrid.In( group );

			inside.Toggle( "Ceiling under the wall plate, with a cornice", "opt_roof_ceiling", "border_top", roof.Ceiling,
				value => { roof.Ceiling = value; changed?.Invoke(); } );

			inside.Toggle( "Skin that ceiling as the floor of a loft", "opt_loft_floor", "layers", roof.LoftFloor,
				value => { roof.LoftFloor = value; changed?.Invoke(); } );
		} );
	}

	static void Direction( ArchIconGrid grid, RidgeRun run, bool active, Action chosen ) {
		grid.Pick( ArchIcons.RidgeAdvice( run ), ArchIcons.RidgeSlug( run ),
			ArchIcons.RidgeGlyph( run ), active, chosen );
	}
}