Editor/Designers/ArchCorniceStage.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

// One staging of a cornice, shared by the designer's turntable and the browser's card art. A whole crowned block,
// because a cornice is judged at a corner: how the mitre closes and where a pier breaks the line are the two things
// a straight run cannot show.
public static class ArchCorniceStage {
	// The kit is what a roof names its cornice through, so a draft nobody has saved has to stand on the shelf under
	// a name of its own for the length of one restage. Every picker hides it again.
	public const string Drafting = ".drafting";

	public static readonly Vector2 Plot = new( 288f, 216f );

	public static bool Hidden( ArchCorniceStyle style ) => string.Equals( style?.Name, Drafting, StringComparison.OrdinalIgnoreCase );

	// The card path: the style is already on the shelf under its own name, so the roof names it and nothing is
	// stood in a scratch slot a paint pass would race over.
	public static ArchStaged Of( ArchCorniceStyle style, ArchKit kit ) => Standing( style, kit, style.Name );

	// The designer path: an unsaved draft has no name a roof could find it by, so it takes the reserved one for
	// the length of one restage. Every picker filters that entry back out.
	public static ArchStaged Drafted( ArchCorniceStyle draft, ArchKit kit ) {
		var staged = draft.Copy();

		staged.Name = Drafting;

		kit.Cornices.RemoveAll( Hidden );
		kit.Cornices.Add( staged );

		return Standing( draft, kit, Drafting );
	}

	static ArchStaged Standing( ArchCorniceStyle style, ArchKit kit, string named ) {
		var plan = new ArchPlan();
		var building = new ArchBuilding { Id = plan.AllocateId(), Name = "Cornice" };
		var shell = ArchBuild.Shell( plan, building, kit, 0, 0f, Vector2.Zero, Plot, false, RoofStyle.Flat, false );

		if ( shell?.Roof is null ) {
			return new ArchStaged( null, Vector3.Zero, 480f );
		}

		plan.Units.Add( building );

		// Pinned to the kit's own storey, or Head cannot answer for the card art what the shell answered here.
		shell.Room.WallHeight = kit.WallHeight;
		shell.Roof.Parapet = true;
		shell.Roof.ParapetHeight = Parapet( style );
		shell.Roof.Cornice = named;

		var head = Head( style, kit );

		return new ArchStaged(
			plan,
			new Vector3( Plot.x * 0.5f, Plot.y * 0.5f, head * 0.82f ),
			MathF.Max( 320f, MathF.Max( Plot.x, head ) * 1.25f ) );
	}

	// Enough parapet under the stack to read as a wall the cornice crowns rather than as a lid on a box.
	public static float Parapet( ArchCorniceStyle style ) => MathF.Max( 16f, style.Rise + 14f );

	public static float Head( ArchCorniceStyle style, ArchKit kit ) => kit.WallHeight + Parapet( style );

	// The near CORNER, not the whole ring: a card framed on the block shows a hairline of trim on a grey box,
	// where the thing being chosen is how the courses stack and how they turn.
	public static BBox Interest( ArchCorniceStyle style, ArchKit kit ) {
		var head = Head( style, kit );
		var reach = style.Projection + kit.WallThickness + 8f;
		var span = MathF.Max( 96f, style.Rise * 4f );

		return new BBox(
			new Vector3( -reach, -reach, head - style.Rise - span * 0.35f ),
			new Vector3( span, span, head + (style.Pier.Stands ? style.Pier.Rise : 0f) + 6f ) );
	}

	// Serialised rather than a typed-out field list: a style carries a nested course list, and a key that forgot
	// one would show yesterday's crown for today's dentils.
	public static string Identity( ArchCorniceStyle style ) => ArchStageKey.Of( "cornice", style.Name, style );

	public static string Describe( ArchCorniceStyle style ) {
		if ( !string.IsNullOrWhiteSpace( style.Detail ) ) {
			return style.Detail;
		}

		return $"{style.Courses.Count} course{(style.Courses.Count == 1 ? "" : "s")}, {style.Rise:0.#} in deep";
	}
}