Editor/Tool/Subtools/ArchBarrierUi.cs
using System;
using System.Linq;
using Editor;
using Sandbox;

namespace Sunless.Architecture;

// Read by the Fence subtool, by a road's own barrier and by a bridge parapet, off the same record. Each form
// takes a Layout rather than a sidebar, so a tool can ask them one per step and a host that still stacks
// sections can lay them all out in a row.
public static class ArchBarrierUi {
	// A barrier's own rows, added to the HOST's sequence rather than opened as a second one - the style decides
	// which of them exist at all, and that branch belongs in one place rather than in every tool that shows a run.
	public static void Steps( ArchWorkflow flow, ArchBarrierSpec spec, Action refresh, Action changed ) {
		flow.Step( "Barrier", Label( spec.Style ), Icon( spec.Style ), step => Styles( step.Layout, spec, step.Chose, changed ) );

		flow.Step( "Ground", spec.Ground.ToString(), GroundIcon( spec.Ground ), step => Grounds( step.Layout, spec, step.Chose, changed ) );

		if ( spec.Style != BarrierStyle.Retaining ) {
			flow.Step( "Gate", spec.GateWidth > 0f ? $"{spec.GateWidth:0} wide" : "None",
				spec.GateWidth > 0f ? "meeting_room" : "block", step => Gate( step.Layout, spec, refresh, changed ) );

			Dressing( flow, spec, refresh, changed );
		}

		flow.Step( "Bays", BayName( spec ), "view_column", step => Bays( step.Layout, spec, changed ) );

		flow.Step( "Disrepair", WearName( spec ), "broken_image", step => Disrepair( step.Layout, spec, changed ) );
	}

	static void Dressing( ArchWorkflow flow, ArchBarrierSpec spec, Action refresh, Action changed ) {
		if ( spec.Style == BarrierStyle.PrecastSlab ) {
			flow.Step( "Slabs", $"{spec.Courses} courses", "view_week", step => Slabs( step.Layout, spec, changed ) );

			flow.Step( "Topping", spec.Droppers > 0 ? $"{spec.Strands} strands" : "None",
				spec.Droppers > 0 ? "bolt" : "block", step => Electric( step.Layout, spec, refresh, changed ) );

			return;
		}

		if ( spec.Style == BarrierStyle.Solid ) {
			flow.Step( "Parapet", spec.Coping ? "Coped" : "Bare", spec.Coping ? "layers" : "rectangle",
				step => Parapet( step.Layout, spec, refresh, changed ) );

			return;
		}

		flow.Step( "Infill", InfillName( spec ), InfillGlyph( spec ), step => Infill( step.Layout, spec, step.Chose, changed ) );
	}

	public static void Build( ToolSidebarWidget panel, ArchBarrierSpec spec, Action refresh, Action changed = null ) {
		ArchSidebarSection.Show( panel, "Barrier.style", "Barrier", group => {
			Styles( group, spec, refresh, changed );
			Grounds( group, spec, refresh, changed );
		} );

		ArchSidebarSection.Show( panel, "Barrier.bays", "Bays", group => Bays( group, spec, changed ) );

		if ( spec.Style == BarrierStyle.Retaining ) {
			ArchSidebarSection.Disclosure( panel, "Barrier.disrepair", "Disrepair", true, group => Disrepair( group, spec, changed ) );

			return;
		}

		ArchSidebarSection.Show( panel, "Barrier.gate", "Gate", group => Gate( group, spec, refresh, changed ) );

		if ( spec.Style == BarrierStyle.PrecastSlab ) {
			ArchSidebarSection.Disclosure( panel, "Barrier.slabs", "Slabs", true, group => Slabs( group, spec, changed ) );
			ArchSidebarSection.Show( panel, "Barrier.electric", "Electric topping", group => Electric( group, spec, refresh, changed ) );
		} else if ( spec.Style == BarrierStyle.Solid ) {
			ArchSidebarSection.Show( panel, "Barrier.parapet", "Parapet", group => Parapet( group, spec, refresh, changed ) );
		} else {
			ArchSidebarSection.Show( panel, "Barrier.infill", "Infill", group => Infill( group, spec, refresh, changed ) );
		}

		ArchSidebarSection.Disclosure( panel, "Barrier.disrepair", "Disrepair", true, group => Disrepair( group, spec, changed ) );
	}

	// The three a deck, a balcony or a roof edge may stand, offered wherever one of them has a guard turned on -
	// so a parapet is picked the same way on all of them and no host grows a style list of its own.
	public static void GuardStyles( Layout group, BarrierStyle style, Action<BarrierStyle> set ) {
		using var grid = ArchIconGrid.In( group );

		foreach ( var standing in ArchGuardStyles.Standing ) {
			var captured = standing;

			grid.Pick( ArchGuardStyles.Hint( captured ), $"guard_{captured}".ToLowerInvariant(), ArchGuardStyles.Icon( captured ),
				style == captured, () => set( captured ) );
		}
	}

	public static void Styles( Layout group, ArchBarrierSpec spec, Action refresh, Action changed ) {
		using var grid = ArchIconGrid.In( group );

		foreach ( var style in Enum.GetValues<BarrierStyle>() ) {
			var captured = style;

			grid.Pick( $"{Label( captured )} — {Hint( captured )}", $"barrier_{captured}".ToLowerInvariant(), Icon( captured ),
				spec.Style == captured, () => { spec.Style = captured; changed?.Invoke(); refresh?.Invoke(); } );
		}
	}

	public static void Grounds( Layout group, ArchBarrierSpec spec, Action refresh, Action changed ) {
		using var grid = ArchIconGrid.In( group );

		foreach ( var ground in Enum.GetValues<BarrierGround>() ) {
			var captured = ground;

			grid.Pick( $"{captured} — {GroundHint( captured )}", $"ground_{captured}".ToLowerInvariant(), GroundIcon( captured ),
				spec.Ground == captured, () => { spec.Ground = captured; changed?.Invoke(); refresh?.Invoke(); } );
		}
	}

	public static void Bays( Layout group, ArchBarrierSpec spec, Action changed ) {
		var retaining = spec.Style == BarrierStyle.Retaining;

		// A retaining wall's head is the grade it holds and its section is battered off the kit, so a typed
		// height and a post size would only fight the ground.
		if ( !retaining ) {
			group.Add( ArchPartUi.Number( "Height", spec.Height, 40f, value => spec.Height = value, changed ) );

			if ( spec.Style != BarrierStyle.Solid ) {
				group.Add( ArchPartUi.Number( "Post size", spec.PostSize, 5f, value => spec.PostSize = value, changed ) );
			}
		}

		group.Add( ArchPartUi.Number( retaining ? "Joint pitch" : "Panel length", spec.PanelLength, 96f, value => spec.PanelLength = value, changed ) );

		group.Add( ArchPartUi.Check( "Divide bays evenly", spec.EvenBays, value => spec.EvenBays = value, changed ) );
	}

	// A zero-width gate is no gate, so the width IS the enable - the two numbers only mean anything together.
	public static void Gate( Layout group, ArchBarrierSpec spec, Action refresh, Action changed ) {
		using ( var grid = ArchIconGrid.In( group ) ) {
			grid.Toggle( "Leave a gate in the run", "opt_gate", "meeting_room", spec.GateWidth > 0f, value => {
				spec.GateWidth = value ? 96f : 0f;
				changed?.Invoke();
				refresh?.Invoke();
			} );
		}

		if ( spec.GateWidth <= 0f ) {
			return;
		}

		group.Add( ArchPartUi.Number( "Gate at", spec.GateAt, 0f, value => spec.GateAt = value, changed ) );
		group.Add( ArchPartUi.Number( "Gate width", spec.GateWidth, 96f, value => spec.GateWidth = value, changed ) );
	}

	public static void Slabs( Layout group, ArchBarrierSpec spec, Action changed ) {
		group.Add( ArchPartUi.Integer( "Courses", spec.Courses, 4, value => spec.Courses = value, changed ) );
		group.Add( ArchPartUi.Number( "Slab thickness", spec.PanelThickness, 2.4f, value => spec.PanelThickness = value, changed ) );
		group.Add( ArchPartUi.Number( "Plinth height", spec.PlinthHeight, 0f, value => spec.PlinthHeight = value, changed ) );
		group.Add( ArchPartUi.Number( "Post overhang", spec.PostOverhang, 0f, value => spec.PostOverhang = value, changed ) );
	}

	// No brackets, no strands to hang off them - the bracket count is what stands the topping up at all.
	public static void Electric( Layout group, ArchBarrierSpec spec, Action refresh, Action changed ) {
		using ( var grid = ArchIconGrid.In( group ) ) {
			grid.Toggle( "Live strands on brackets over the wall", "opt_electric", "bolt", spec.Droppers > 0, value => {
				spec.Droppers = value ? 2 : 0;
				changed?.Invoke();
				refresh?.Invoke();
			} );
		}

		if ( spec.Droppers <= 0 ) {
			return;
		}

		group.Add( ArchPartUi.Integer( "Brackets", spec.Droppers, 2, value => spec.Droppers = value, changed ) );
		group.Add( ArchPartUi.Integer( "Strands", spec.Strands, 6, value => spec.Strands = value, changed ) );
		group.Add( ArchPartUi.Number( "Bracket lean", spec.DropperLean, 35f, value => spec.DropperLean = value, changed ) );
		group.Add( ArchPartUi.Number( "Bracket length", spec.DropperLength, 16f, value => spec.DropperLength = value, changed ) );
	}

	// A plastered boundary wall: the section and the cap, and nothing about posts or rails it does not have.
	public static void Parapet( Layout group, ArchBarrierSpec spec, Action refresh, Action changed ) {
		group.Add( ArchPartUi.Number( "Wall thickness", spec.PanelThickness, 6f, value => spec.PanelThickness = value, changed ) );

		using ( var grid = ArchIconGrid.In( group ) ) {
			grid.Toggle( "Coping over the head, oversailing both faces", "opt_parapet_coping", "layers", spec.Coping,
				value => { spec.Coping = value; changed?.Invoke(); refresh?.Invoke(); } );
		}

		if ( !spec.Coping ) {
			return;
		}

		group.Add( ArchPartUi.Number( "Coping height", spec.CopingHeight, 2.5f, value => spec.CopingHeight = value, changed ) );
		group.Add( ArchPartUi.Number( "Coping oversail", spec.CopingProud, 1.2f, value => spec.CopingProud = value, changed ) );
	}

	public static void Infill( Layout group, ArchBarrierSpec spec, Action refresh, Action changed ) {
		// A catenary's infill IS its conductors, and how far they sag comes off the kit as a share of the span.
		if ( spec.Style == BarrierStyle.Catenary ) {
			group.Add( ArchPartUi.Integer( "Strands", spec.Strands, 6, value => spec.Strands = value, changed ) );

			return;
		}

		group.Add( ArchPartUi.Integer( "Rails", spec.Rails, 2, value => spec.Rails = value, changed ) );

		if ( spec.Style != BarrierStyle.Picket ) {
			return;
		}

		using var grid = ArchIconGrid.In( group );

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

			grid.Pick( $"Top: {captured}", $"picket_{captured}".ToLowerInvariant(), TopIcon( captured ),
				spec.Top == captured, () => { spec.Top = captured; changed?.Invoke(); refresh?.Invoke(); } );
		}
	}

	// Wear is judged after a run is standing, never before it is drawn, so it is asked last.
	public static void Disrepair( Layout group, ArchBarrierSpec spec, Action changed ) {
		group.Add( ArchPartUi.Integer( "Seed", spec.Seed, 0, value => spec.Seed = value, changed ) );
		group.Add( ArchPartUi.Number( "Wear", spec.Wear, 0f, value => spec.Wear = value, changed ) );
		group.Add( ArchPartUi.Number( "Broken", spec.Broken, 0f, value => spec.Broken = value, changed ) );
		group.Add( ArchPartUi.Integer( "Course vary", spec.CourseVary, 0, value => spec.CourseVary = value, changed ) );
	}

	// Ten characters or fewer, every one of them: a tile caption is derived from the name before the dash, and
	// ArchIconGrid keeps only the first word of anything longer.
	public static string Label( BarrierStyle style ) => style switch {
		BarrierStyle.PrecastSlab => "Precast",
		BarrierStyle.PostAndRail => "Post rail",
		BarrierStyle.Solid => "Parapet",
		_ => style.ToString()
	};

	static string Hint( BarrierStyle style ) => style switch {
		BarrierStyle.PrecastSlab => "stacked slabs between posts, the township boundary wall",
		BarrierStyle.Palisade => "vertical steel pales on rails",
		BarrierStyle.Guardrail => "a crash barrier on posts",
		BarrierStyle.PostAndRail => "open horizontal rails",
		BarrierStyle.Bollards => "free-standing posts, no infill",
		BarrierStyle.Retaining => "a battered wall holding the grade behind it",
		BarrierStyle.Catenary => "conductors sagging between poles",
		BarrierStyle.Solid => "a plastered wall with an optional coping",
		_ => "pickets on rails between posts"
	};

	static string GroundHint( BarrierGround ground ) => ground switch {
		BarrierGround.Step => "every bay level, dropping between them",
		BarrierGround.Level => "the fall ignored altogether",
		_ => "each bay tilted to follow the slope"
	};

	static string BayName( ArchBarrierSpec spec ) => spec.Style == BarrierStyle.Retaining
		? $"{spec.PanelLength:0} pitch"
		: $"{spec.Height:0} high";

	static string WearName( ArchBarrierSpec spec ) => spec.Wear > 0f || spec.Broken > 0f
		? $"{spec.Wear:0.##} wear"
		: "None";

	static string InfillName( ArchBarrierSpec spec ) => spec.Style switch {
		BarrierStyle.Catenary => $"{spec.Strands} strands",
		BarrierStyle.Picket => $"{spec.Rails} rails, {spec.Top}",
		_ => $"{spec.Rails} rails"
	};

	static string InfillGlyph( ArchBarrierSpec spec ) => spec.Style switch {
		BarrierStyle.Catenary => "power_input",
		BarrierStyle.Picket => TopIcon( spec.Top ),
		_ => "table_rows"
	};

	public static string Icon( BarrierStyle style ) => style switch {
		BarrierStyle.PrecastSlab => "view_week",
		BarrierStyle.Palisade => "density_medium",
		BarrierStyle.Guardrail => "horizontal_split",
		BarrierStyle.PostAndRail => "table_rows",
		BarrierStyle.Bollards => "more_vert",
		BarrierStyle.Retaining => "foundation",
		BarrierStyle.Catenary => "power_input",
		BarrierStyle.Solid => "rectangle",
		_ => "fence"
	};

	static string GroundIcon( BarrierGround ground ) => ground switch {
		BarrierGround.Step => "stairs",
		BarrierGround.Level => "horizontal_rule",
		_ => "landscape"
	};

	static string TopIcon( PicketTop top ) => top switch {
		PicketTop.Pointed => "change_history",
		PicketTop.Flat => "horizontal_rule",
		_ => "architecture"
	};
}