Editor/Designers/ArchPillarDesigner.cs
using System;
using System.Collections.Generic;
using Editor;
using Sandbox;

namespace Sunless.Architecture;

// Save writes back to .pillartype.json, so what's tuned here becomes the shared kind.
public sealed class ArchPillarDesigner : ArchDesignerDialog {
	const int PreviewColumnsX = ArchPillarStage.ColumnsX;
	const int PreviewColumnsY = ArchPillarStage.ColumnsY;

	readonly ArchTool tool;
	readonly Action<ArchPillarPart> applied;
	readonly ArchPillarPart draft;
	string name;
	string title;
	float height;

	public ArchPillarDesigner( Widget parent, ArchTool tool, ArchPillarPart initial, ArchPillarType type, Action<ArchPillarPart> applied )
		: base( parent, "Pillar Designer", "view_column" ) {
		this.tool = tool;
		this.applied = applied;
		draft = (initial ?? new ArchPillarPart()).Section();
		name = type?.Name ?? draft.Type;
		title = type?.Title ?? "Pillar";
		height = tool.Kit.WallHeight;

		Assemble(
			tool.Kit,
			"Live generated colonnade",
			$"Drag the preview to orbit. A {PreviewColumnsX} x {PreviewColumnsY} bay of the kind you are designing, through the same shaft, footing and span generators the plan builds with.",
			$"Pillar types: Assets/{ArchPillarCatalog.Directory}" );
	}

	protected override string SaveLabel => "Save Type";

	protected override void Populate() {
		var form = Form.Layout;

		form.Add( Heading( "Identity" ) );
		form.Add( Text( "Name", name, value => name = value ) );
		form.Add( Text( "Title", title, value => title = value ) );

		form.Add( Heading( "Preview" ) );
		form.Add( Number( "Storey height", height, value => height = MathF.Max( 24f, value ) ) );
		form.Add( Number( "Bay x", draft.Spacing.x, value => draft.Spacing = draft.Spacing.WithX( MathF.Max( 24f, value ) ) ) );
		form.Add( Number( "Bay y", draft.Spacing.y, value => draft.Spacing = draft.Spacing.WithY( MathF.Max( 24f, value ) ) ) );

		var options = new ToolSidebarWidget();

		ArchPillarUi.Build( options, draft, tool.FindPillarType( name ), Repopulate, Stage.Restage );

		form.Add( options );
	}

	protected override ArchStaged Staged() => ArchPillarStage.Of( draft, height );

	protected override string Diagnose() {
		var column = Staged().Plan.Buildings[0].Rooms[0].Pillars[0];
		var spanning = column.Span != PillarSpan.None;
		var shaft = spanning ? MathF.Max( 8f, height - column.SpanBand ) : height;
		var thinnest = MathF.Min( column.Width, column.Depth );

		return string.Join( "\n",
			$"type: {name} ({title}), placement {column.Placement}",
			$"storey height: {height:0.##}",
			$"section: {column.Width:0.##} x {column.Depth:0.##}, reach {column.Reach:0.##}, seat {column.Seat:0.##}",
			$"slenderness: {(thinnest > 0.01f ? height / thinnest : 0f):0.##} to 1 over the storey, "
				+ $"{(thinnest > 0.01f ? shaft / thinnest : 0f):0.##} to 1 over the shaft",
			$"shaft: {shaft:0.##} of {height:0.##}, span band {(spanning ? column.SpanBand : 0f):0.##}",
			$"footing: {column.Footing} spread {column.FootingSpread:0.##} deep {column.FootingHeight:0.##} buried {column.FootingBuried:0.##}",
			$"plinth: {column.Plinth} {column.PlinthHeight:0.##} over {column.PlinthOversize:0.##}",
			$"capital: {column.Capital} {column.CapitalHeight:0.##} over {column.CapitalOversize:0.##}",
			$"span: {column.Span} x{column.SpanAlongX} y{column.SpanAlongY} perimeter {column.PerimeterOnly} "
				+ $"beam {column.SpanDepth:0.##}x{column.SpanHeight:0.##} arch rise {column.ArchRise:0.##} ring {column.ArchRing:0.##}",
			$"span thickness: {column.SpanThickness( true ):0.##} along x, {column.SpanThickness( false ):0.##} along y "
				+ $"(authored {column.SpanDepth:0.##}, pier {column.Depth:0.##} x {column.Width:0.##})",
			$"bays: {PreviewColumnsX} x {PreviewColumnsY} at {column.Spacing.x:0.##} x {column.Spacing.y:0.##}, "
				+ $"clear span {column.Spacing.x - column.Width:0.##} x {column.Spacing.y - column.Depth:0.##}",
			$"runs: {ArchAsks.PillarRunCount( column )} over {ArchAsks.PillarCarriedCount( column )} of {column.TotalCount} piers",
			$"tops: {(column.Capital ? "capital" : spanning ? column.Span.ToString().ToLowerInvariant() : "bare shaft")}" );
	}

	protected override void Apply() {
		applied?.Invoke( Snapshot() );
		Close();
	}

	// The type is what a colonnade stands from, so the whole column goes into it.
	protected override void SaveTemplate() {
		var saved = Snapshot();

		if ( string.IsNullOrWhiteSpace( saved.Type ) ) {
			Log.Warning( "Architecture: the pillar type needs a name before it can be saved." );
			return;
		}

		var type = new ArchPillarType {
			Name = saved.Type,
			Title = string.IsNullOrWhiteSpace( title ) ? saved.Type : title,
			Description = tool.FindPillarType( saved.Type )?.Description ?? "",
			Icon = tool.FindPillarType( saved.Type )?.Icon ?? "view_column",
			Column = saved
		};

		if ( !ArchAsks.SavePillarType( type ) ) {
			Log.Warning( $"Architecture: could not write the pillar type '{type.Name}'." );
			return;
		}

		tool.ReloadPillarTypes();
		applied?.Invoke( Snapshot() );
		Close();
	}

	ArchPillarPart Snapshot() {
		var result = draft.Section();

		result.Type = string.IsNullOrWhiteSpace( name ) ? draft.Type : name.Trim();

		return result;
	}
}