Editor/Cabinet/ArchCabinetStage.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace Sunless.Architecture;

// One staging of a cabinet type, shared by the designer's turntable and the browser's card art. A short RUN rather
// than a single carcass, because a module, the reveal between two of them and the counter running over both are the
// things an author is judging - and a prefabbed type stands its own art here through the same fitting pass the
// scene uses, so what a card shows is what the room gets.
public static class ArchCabinetStage {
	public const int Modules = 3;

	public static ArchStaged Of( ArchCabinetType type ) {
		if ( type is null ) {
			return new ArchStaged( null, Vector3.Zero, 320f );
		}

		var module = MathF.Max( 6f, type.Width );
		var run = module * Modules;
		var height = MathF.Max( 24f, Stood( type ) );

		// FACING THE CAMERA. The preview looks along +y at its quarter view, so a run whose fronts also point +y
		// shows the card its open back - the doors, the drawers and the whole reason to browse are on the far side.
		var facing = new Vector2( 0f, -1f );

		var cabinets = new ArchCabinetPart {
			Id = 2,
			Name = "Cabinets",
			Type = type.Name,
			Anchor = Vector2.Zero,
			Outward = facing,
			Width = run
		};

		var room = new ArchRoom {
			Id = 1,
			WallHeight = MathF.Max( 96f, height + 24f ),
			HasFloor = false,
			RaisedFoundation = false,
			Cabinets = new List<ArchCabinetPart> { cabinets }
		};

		var building = new ArchBuilding {
			Id = 1,
			Rooms = new List<ArchRoom> { room },
			StoreyHeight = room.WallHeight
		};

		// A hanging run seats itself off the standard datum with nothing under it, so the frame has to reach the
		// top of where it hangs rather than the top of the carcass alone.
		var seat = type.Tier == CabinetTier.Hanging ? Datum( type ) : 0f;
		var middle = facing * (MathF.Max( 4f, type.Depth ) * 0.5f);

		return new ArchStaged(
			new ArchPlan { Units = new List<ArchUnit> { building } },
			new Vector3( middle.x, middle.y, seat + height * 0.5f ),
			MathF.Max( 90f, MathF.Max( run, seat + height ) * 1.75f ) );
	}

	// The draft a designer is editing is not in the kit yet - and may be about to be renamed into it - so the stage
	// is handed a kit that offers exactly it. The palette comes from the real one, or a card would preview the
	// blockout materials instead of the map's.
	public static ArchKit Staging( ArchKit kit, ArchCabinetType draft ) {
		var staged = new ArchKit {
			Cabinets = new List<ArchCabinetType> { draft }
		};

		if ( kit is not null ) {
			staged.Palette = kit.Palette;
			staged.WallHeight = kit.WallHeight;
			staged.WallThickness = kit.WallThickness;
		}

		return staged;
	}

	public static string Identity( ArchCabinetType type ) {
		return type is null ? "cabinet.none" : ArchStageKey.Of( "cabinet", type.Name ?? "", type );
	}

	public static string Describe( ArchCabinetType type ) {
		if ( type is null ) {
			return "";
		}

		if ( !string.IsNullOrWhiteSpace( type.Detail ) ) {
			return type.Detail;
		}

		var made = type.Prefabbed
			? "art"
			: ArchCabinetUi.Named( type.On( ArchCabinetMounts.Front )?.Front ?? CabinetFront.Slab );

		return $"{type.Width:0} × {type.Depth:0} · {made}";
	}

	// What a card is CATEGORISED by: how it is made first, because a prefabbed type and a generated one are
	// authored in completely different halves of the designer.
	public static string Category( ArchCabinetType type ) {
		if ( type is null ) {
			return "Cabinet";
		}

		return type.Prefabbed ? "Prefab" : type.Tier switch {
			CabinetTier.Hanging => "Wall",
			CabinetTier.Tall => "Tall",
			_ => "Base"
		};
	}

	public static string Glyph( ArchCabinetType type ) {
		return type is { Prefabbed: true } ? "category" : ArchCabinetUi.Glyph( type?.Tier ?? CabinetTier.Base );
	}

	static float Stood( ArchCabinetType type ) {
		return type.Plinth + type.Carcass + type.Counter;
	}

	static float Datum( ArchCabinetType type ) {
		return type.Datum > 1f ? type.Datum : ArchCabinetShape.HungDatum;
	}
}