Editor/Pillar/ArchPillarUi.cs
using System;
using System.Collections.Generic;
using Editor;
using Sandbox;

namespace Sunless.Architecture;

// No section PICKER: a pillar's look comes from its TYPE, filtered by building type.
public static class ArchPillarUi {
	// One kind is the normal case; a single-entry grid still reads as a choice.
	public static void Kinds( ToolSidebarWidget panel, IReadOnlyList<ArchPillarType> offered, string chosen, Action<ArchPillarType> adopt ) {
		if ( offered.Count == 0 ) {
			panel.Layout.Add( ArchSidebarLayout.Advice( "No pillar types are authored — add one to Assets/arch/pillars." ) );

			return;
		}

		using var grid = ArchIconGrid.In( panel.AddGroup( offered.Count == 1 ? "Pillar" : "Pillar type" ) );

		foreach ( var type in offered ) {
			var captured = type;

			grid.Pick( $"{captured.Title} - {captured.Description}", ArchIcons.PillarSlug( captured.Name ), captured.Icon,
				string.Equals( chosen, captured.Name, StringComparison.OrdinalIgnoreCase ), () => adopt( captured ) );
		}
	}

	// Room entry REPLACED, not patched - one field list stops a setting silently going missing.
	public static ArchPillarPart Retype( ArchTool tool, ArchPillarPart pillar, ArchPillarType type ) {
		if ( tool.Plan.RoomStanding( pillar ) is not { } room ) {
			return pillar;
		}

		var dressed = type.Standing( pillar.Origin, pillar.BaseHeight, pillar.Height );

		dressed.Id = pillar.Id;
		dressed.Name = pillar.Name;
		dressed.Yaw = pillar.Yaw;
		dressed.CountX = pillar.CountX;
		dressed.CountY = pillar.CountY;
		dressed.Spacing = pillar.Spacing;

		room.Pillars[room.Pillars.IndexOf( pillar )] = dressed;

		tool.Touch( "Retype Pillars" );

		return dressed;
	}

	// The type gates which groups appear - a plain post has no arcade or pad to tune. A caller with nothing to
	// bridge BETWEEN its columns says so, rather than being offered a run that would build nothing.
	public static void Build( ToolSidebarWidget panel, ArchPillarPart pillar, ArchPillarType type, Action refresh, Action changed, bool spans = true ) {
		Shaft( panel, pillar, type, false, refresh, changed );
		Footing( panel, pillar, type, refresh, changed );

		if ( spans ) {
			Span( panel, pillar, type, refresh, changed );
		}
	}

	public static void Sizes( ToolSidebarWidget panel, ArchPillarPart pillar, ArchPillarType type, Action refresh, Action changed ) {
		var grid = panel.AddGroup( "Grid" );

		grid.Add( ArchPartUi.Integer( "Columns x", pillar.CountX, 3, value => pillar.CountX = value, changed ) );
		grid.Add( ArchPartUi.Integer( "Columns y", pillar.CountY, 2, value => pillar.CountY = value, changed ) );
		grid.Add( ArchPartUi.Number( "Bay x", pillar.Spacing.x, 240f, value => pillar.Spacing = pillar.Spacing.WithX( value ), changed ) );
		grid.Add( ArchPartUi.Number( "Bay y", pillar.Spacing.y, 240f, value => pillar.Spacing = pillar.Spacing.WithY( value ), changed ) );

		Shaft( panel, pillar, type, true, refresh, changed );
		Footing( panel, pillar, type, refresh, changed );
		Span( panel, pillar, type, refresh, changed );
		Spanning( panel, pillar, type, changed );
	}

	static bool Wanted( ArchPillarType type, ArchPillarGroup group ) => type?.Wants( group ) ?? true;

	// Height belongs to a standing pillar - a draft takes it from whatever it lands under.
	static void Shaft( ToolSidebarWidget panel, ArchPillarPart pillar, ArchPillarType type, bool standing, Action refresh, Action changed ) {
		var section = Wanted( type, ArchPillarGroup.Section );
		var dressed = Wanted( type, ArchPillarGroup.Ends );

		if ( !section && !dressed ) {
			return;
		}

		var group = panel.AddGroup( standing ? "Shaft" : "Section" );

		if ( section ) {
			if ( standing ) {
				group.Add( ArchPartUi.Number( "Height (0 reaches the soffit)", pillar.Height, 0f, value => pillar.Height = value, changed ) );

				using ( var seat = ArchIconGrid.In( group ) ) {
					seat.Toggle( "Founded — keep the authored foot instead of seating on whatever is poured under it",
						"opt_founded", "anchor", pillar.Founded,
						value => { pillar.Founded = value; refresh?.Invoke(); changed?.Invoke(); } );
				}
			}

			group.Add( ArchPartUi.Number( "Width", pillar.Width, 20f, value => pillar.Width = value, changed ) );
			group.Add( ArchPartUi.Number( "Depth", pillar.Depth, 20f, value => pillar.Depth = value, changed ) );
			group.Add( ArchPartUi.Integer( "Sides", pillar.Sides, 0, value => pillar.Sides = Math.Max( 0, value ), changed ) );
		}

		if ( !dressed ) {
			return;
		}

		using ( var ends = ArchIconGrid.In( group ) ) {
			ends.Toggle( "Plinth under each column", "opt_plinth", "vertical_align_bottom", pillar.Plinth,
				value => { pillar.Plinth = value; refresh?.Invoke(); changed?.Invoke(); } );

			ends.Toggle( "Capital on top of each column", "opt_capital", "vertical_align_top", pillar.Capital,
				value => { pillar.Capital = value; refresh?.Invoke(); changed?.Invoke(); } );
		}

		if ( pillar.Plinth ) {
			group.Add( ArchPartUi.Number( "Plinth height", pillar.PlinthHeight, 8f, value => pillar.PlinthHeight = value, changed ) );
			group.Add( ArchPartUi.Number( "Plinth spread", pillar.PlinthOversize, 3f, value => pillar.PlinthOversize = value, changed ) );
		}

		if ( !pillar.Capital ) {
			return;
		}

		group.Add( ArchPartUi.Number( "Capital height", pillar.CapitalHeight, 8f, value => pillar.CapitalHeight = value, changed ) );
		group.Add( ArchPartUi.Number( "Capital spread", pillar.CapitalOversize, 3f, value => pillar.CapitalOversize = value, changed ) );

		// The head's shapes ARE the pad's, so they read off the same hints - None being the pad's own way of saying
		// nothing, which the Capital toggle above already says.
		using var heads = ArchIconGrid.In( group );

		foreach ( var value in Enum.GetValues<PillarFooting>() ) {
			if ( value == PillarFooting.None ) {
				continue;
			}

			var captured = value;

			heads.Pick( $"Capital: {FootingHint( captured )}", $"footing_{captured}".ToLowerInvariant(), FootingGlyph( captured ),
				pillar.CapitalShape == captured, () => { pillar.CapitalShape = captured; refresh?.Invoke(); changed?.Invoke(); } );
		}
	}

	// The pad is what stops a column reading as a post on the ground.
	static void Footing( ToolSidebarWidget panel, ArchPillarPart pillar, ArchPillarType type, Action refresh, Action changed ) {
		if ( !Wanted( type, ArchPillarGroup.Footing ) ) {
			return;
		}

		var group = panel.AddGroup( "Footing" );

		using ( var grid = ArchIconGrid.In( group ) ) {
			foreach ( var value in Enum.GetValues<PillarFooting>() ) {
				var captured = value;

				grid.Pick( $"Footing: {FootingHint( captured )}", $"footing_{captured}".ToLowerInvariant(), FootingGlyph( captured ),
					pillar.Footing == captured, () => { pillar.Footing = captured; refresh?.Invoke(); changed?.Invoke(); } );
			}
		}

		if ( pillar.Footing == PillarFooting.None ) {
			return;
		}

		group.Add( ArchPartUi.Number( "Spread", pillar.FootingSpread, 8f, value => pillar.FootingSpread = value, changed ) );
		group.Add( ArchPartUi.Number( "Depth", pillar.FootingHeight, 10f, value => pillar.FootingHeight = value, changed ) );
		group.Add( ArchPartUi.Number( "Buried", pillar.FootingBuried, 4f, value => pillar.FootingBuried = value, changed ) );
	}

	static void Span( ToolSidebarWidget panel, ArchPillarPart pillar, ArchPillarType type, Action refresh, Action changed ) {
		if ( !Wanted( type, ArchPillarGroup.Span ) ) {
			return;
		}

		using ( var grid = ArchIconGrid.In( panel.AddGroup( "Span between them" ) ) ) {
			foreach ( var value in Enum.GetValues<PillarSpan>() ) {
				var captured = value;

				grid.Pick( $"Span: {ArchSpanUi.Hint( captured )}", $"span_{captured}".ToLowerInvariant(), ArchSpanUi.Glyph( captured ),
					pillar.Span == captured, () => { pillar.Span = captured; refresh?.Invoke(); changed?.Invoke(); } );
			}
		}

		if ( pillar.Span == PillarSpan.None ) {
			return;
		}

		using var runs = ArchIconGrid.In( panel.AddGroup( "Which way it runs" ) );

		runs.Toggle( "Span along x", "span_along_x", "swap_horiz", pillar.SpanAlongX,
			value => { pillar.SpanAlongX = value; changed?.Invoke(); } );

		runs.Toggle( "Span along y", "span_along_y", "swap_vert", pillar.SpanAlongY,
			value => { pillar.SpanAlongY = value; changed?.Invoke(); } );

		runs.Toggle( "Ring the grid only — no runs through the middle", "span_perimeter", "crop_square", pillar.PerimeterOnly,
			value => { pillar.PerimeterOnly = value; changed?.Invoke(); } );
	}

	static void Spanning( ToolSidebarWidget panel, ArchPillarPart pillar, ArchPillarType type, Action changed ) {
		if ( pillar.Span == PillarSpan.None || !Wanted( type, ArchPillarGroup.Span ) ) {
			return;
		}

		var span = panel.AddGroup( pillar.Span.ToString() );

		if ( pillar.Curved ) {
			span.Add( ArchPartUi.Number( "Rise", pillar.ArchRise, 48f, value => pillar.ArchRise = value, changed ) );

			if ( pillar.Span == PillarSpan.Arch ) {
				span.Add( ArchPartUi.Number( "Ring", pillar.ArchRing, 10f, value => pillar.ArchRing = value, changed ) );
			}

			span.Add( ArchPartUi.Integer( "Segments", pillar.ArchSegments, 10, value => pillar.ArchSegments = value, changed ) );

			return;
		}

		span.Add( ArchPartUi.Number( "Depth", pillar.SpanDepth, 14f, value => pillar.SpanDepth = value, changed ) );
		span.Add( ArchPartUi.Number( "Height", pillar.SpanHeight, 18f, value => pillar.SpanHeight = value, changed ) );
	}

	static string FootingHint( PillarFooting value ) => value switch {
		PillarFooting.Square => "a plain block pad",
		PillarFooting.Bevelled => "a pad tapering up to the shaft, so there is no shelf to stand on",
		PillarFooting.Stepped => "two pads, the lower one wider",
		_ => "none - the column stands straight on its base"
	};

	static string FootingGlyph( PillarFooting value ) => value switch {
		PillarFooting.Square => "crop_square",
		PillarFooting.Bevelled => "change_history",
		PillarFooting.Stepped => "stairs",
		_ => "block"
	};

}