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

namespace Sunless.Architecture;

// Treatments don't read on buttons - judged against a real elevation with a door and window.
public sealed class ArchWallDesigner : ArchDesignerDialog {
	const float PreviewSpan = ArchWallStage.Span;

	readonly ArchKit kit;
	readonly Action<ArchWallPreset> applied;
	readonly ArchWallPreset draft;

	public ArchWallDesigner( Widget parent, ArchKit kit, ArchWallPreset initial, Action<ArchWallPreset> applied )
		: base( parent, "Wall Designer", "horizontal_rule" ) {
		this.kit = kit;
		this.applied = applied;
		draft = ArchWallPreset.Copy( initial );

		Assemble(
			kit,
			"Live generated elevation",
			"Drag the preview to orbit. One wall with a door and a window in it, through the same panel, band, siding and trim generators the plan builds with.",
			$"Wall treatments: Assets/{ArchWallCatalog.Directory}" );
	}

	protected override string SaveLabel => "Save Treatment";

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

		form.Add( Heading( "Identity" ) );
		form.Add( Text( "Name", draft.Name, value => draft.Name = value ) );

		var options = new ToolSidebarWidget();

		ArchWallUi.Build( options, kit, draft, Repopulate, Stage.Restage, openings: false );

		form.Add( options );
	}

	protected override ArchStaged Staged() => ArchWallStage.Of( Snapshot(), kit );

	protected override string Diagnose() {
		var unit = Snapshot();
		var height = Storey( unit );
		var bands = ArchWallTreatments.Ordered( unit.Bands, height ).ToList();
		var cursor = 0f;
		var fields = new List<string>();

		foreach ( var band in bands ) {
			var at = band.At( height );

			fields.Add( $"{cursor:0.#} to {at:0.#}{(band.AtHead ? " (head)" : "")} {band.Field}"
				+ $"{(band.Course ? $", {band.Seat.ToString().ToLowerInvariant()} course "
					+ $"{(band.CourseHeight > 0.01f ? band.CourseHeight : kit.WallBandHeight):0.##} high x "
					+ $"{(band.CourseDepth > 0.01f ? band.CourseDepth : kit.WallBandDepth):0.##} proud" : "")}" );
			cursor = at;
		}

		fields.Add( $"{cursor:0.#} to {height:0.#} "
			+ (unit.Cladding != WallCladding.None ? "Siding" : unit.Exterior ? "WallExterior" : "WallInterior") );

		return string.Join( "\n",
			$"treatment: {unit.Name}",
			$"wall: {height:0.##} tall ({(unit.Height > 0f ? "authored" : "inherited")}), "
				+ $"{(unit.Thickness > 0f ? unit.Thickness : kit.WallThickness):0.##} thick ({(unit.Thickness > 0f ? "authored" : "inherited")})",
			$"bands: {bands.Count}",
			"fields: " + string.Join( " | ", fields ),
			$"cladding: {unit.Cladding} — " + (unit.Cladding switch {
				WallCladding.Weatherboard => $"{kit.SidingCourse:0.##} courses standing {kit.SidingProud:0.##} proud",
				WallCladding.Profiled => $"{kit.SheetPitch:0.##} pitch, {kit.SheetCrest:0.##} crest, {kit.SheetProud:0.##} proud",
				_ => "flat"
			}),
			$"pilasters: " + (unit.Pilasters.Stands
				? $"{unit.Pilasters.Bay:0.##} bay at most over {PreviewSpan:0.##} "
					+ $"= {ArchDivide.AtMost( PreviewSpan, unit.Pilasters.Bay ).Count + 1} strips, "
					+ $"{unit.Pilasters.Width:0.##} wide standing {unit.Pilasters.Depth:0.##} proud"
				: "none"),
			$"wainscot: {unit.Wainscot} to {kit.WainscotHeight:0.##} with a {kit.ChairRailHeight:0.##} rail",
			$"trims: skirting {unit.Baseboard}, cap {unit.Cap} at {ArchWallSection.CapHeight( unit, kit ):0.##} "
				+ $"oversailing {ArchWallSection.CapOverhang( unit, kit ):0.##}",
			$"faces: exterior {unit.Exterior}" );
	}

	float Storey( ArchWallPreset unit ) => unit.Height > 1f ? unit.Height : kit.WallHeight;

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

	// Folded into the held kit, so the grid offers it next rebuild without a reload.
	protected override void SaveTemplate() {
		var saved = Snapshot();

		if ( !new ArchWallCatalog().Save( saved ) ) {
			Log.Warning( "Architecture: the wall treatment needs a name before it can be saved." );
			return;
		}

		var index = kit.Walls.FindIndex( preset => string.Equals( preset.Name, saved.Name, StringComparison.OrdinalIgnoreCase ) );

		if ( index >= 0 ) {
			kit.Walls[index] = saved;
		} else {
			kit.Walls.Add( saved );
		}

		applied?.Invoke( ArchWallPreset.Copy( saved ) );
		Close();
	}

	ArchWallPreset Snapshot() {
		var result = ArchWallPreset.Copy( draft );

		result.Name = string.IsNullOrWhiteSpace( draft.Name ) ? "wall" : draft.Name.Trim();
		result.Height = ArchGridService.Least( result.Height, 0f );
		result.Thickness = ArchGridService.Least( result.Thickness, 0f );

		// Held under the head, not dropped: the typed height is usually one keystroke short.
		foreach ( var band in result.Bands.Where( band => !band.AtHead ) ) {
			band.Height = Math.Clamp( ArchGridService.Fine( band.Height ), 0f, Storey( result ) );
		}

		return result;
	}
}