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

namespace Sunless.Architecture;

public enum CabinetTier {
	Auto,
	Base,
	Hanging,
	Tall
}

public enum CabinetCorner {
	Diagonal,
	Blind,
	None
}

public enum CabinetFront {
	None,
	Slab,
	Shaker,
	Panel,
	Glass
}

public enum CabinetSlot {
	Module,
	Corner,
	End,
	Filler
}

public sealed class ArchCabinetCarcass : IArchAimedPrefab {
	public CabinetSlot Slot { get; set; }
	public string Prefab { get; set; } = "";
	public bool Fits { get; set; }

	public Vector3 Anchor { get; set; }
	public Vector3 AnchorAlong { get; set; }
	public Angles AnchorTurn { get; set; }

	public bool Scales => Fits;

	public bool Standing => !string.IsNullOrWhiteSpace( Prefab );

	public ArchCabinetCarcass Copy() => (ArchCabinetCarcass)MemberwiseClone();
}

public enum CabinetGrip {
	Bar,
	Knob,
	Cup
}

public sealed class ArchCabinetFitting : IArchAimedPrefab {
	public string Mount { get; set; } = "";
	public CabinetFront Front { get; set; } = CabinetFront.Slab;
	public string Prefab { get; set; } = "";
	public bool Fits { get; set; }

	public bool Scales => Fits;

	public Vector3 Anchor { get; set; }
	public Vector3 AnchorAlong { get; set; }
	public Angles AnchorTurn { get; set; }

	public bool Prefabbed => !string.IsNullOrWhiteSpace( Prefab );

	public bool Standing => Prefabbed || Front != CabinetFront.None;
}

public sealed class ArchCabinetType : IArchNamed {
	public string Name { get; set; } = "";
	public string Detail { get; set; } = "";
	public CabinetTier Tier { get; set; } = CabinetTier.Base;

	public const float NominalWidth = 23.625f;
	public const float NominalDepth = 23.625f;
	public const float HungDepth = 12.9921f;

	// Ceiling, not exact — runs divide so no module exceeds this
	public float Width { get; set; } = NominalWidth;
	public float Depth { get; set; } = NominalDepth;
	public float Height { get; set; } = 28.375f;

	public float PlinthHeight { get; set; } = 5.5f;
	public float PlinthRecess { get; set; } = 2f;

	public float Datum { get; set; }

	public float TopThickness { get; set; } = 1.5625f;
	public float TopDepth { get; set; }
	public float TopOversail { get; set; } = 1f;
	public float TopEndOversail { get; set; }
	public float TopBackOversail { get; set; }

	public CabinetCorner Corner { get; set; } = CabinetCorner.Diagonal;

	public float FrontSplit { get; set; }

	// Exaggerated for readability at 128 px/m
	public float PanelThickness { get; set; } = 1.25f;
	public float FrontThickness { get; set; } = 1.25f;
	public float Reveal { get; set; } = 0.25f;

	public float PanelInset { get; set; } = 0.625f;
	public float PanelRaise { get; set; } = 0.375f;
	public float PanelMargin { get; set; } = 1.25f;

	public float CorniceHeight { get; set; }
	public float CorniceProjection { get; set; } = 1f;
	public float CorniceOversail { get; set; } = 1f;

	public int Shelves { get; set; } = 1;
	public int DrawerRows { get; set; }
	public float DrawerHeight { get; set; } = 7.0625f;

	public bool Handles { get; set; } = true;
	public CabinetGrip Grip { get; set; } = CabinetGrip.Bar;
	public float HandleLength { get; set; } = 5f;
	public float HandleThickness { get; set; } = 1f;
	public float HandleStandoff { get; set; } = 1.5f;

	public bool Interior { get; set; }

	public List<ArchCabinetFitting> Fittings { get; set; } = new();

	// Stated, not derived — clearing a prefab must not silently revert to boxes
	public bool Prefabbed { get; set; }

	public bool Hidden { get; set; }

	public List<ArchCabinetCarcass> Carcasses { get; set; } = new();

	public string MaterialGroup { get; set; } = "";

	public float Carcass => MathF.Max( 4f, Height );

	public float Plinth => Tier == CabinetTier.Hanging ? 0f : MathF.Max( 0f, PlinthHeight );

	public float Counter => Tier == CabinetTier.Hanging ? 0f : MathF.Max( 0f, TopThickness );

	public bool Splits => FrontSplit > 1f && FrontSplit < Carcass - 1f;

	public ArchCabinetFitting On( string mount ) {
		return Fittings?.FirstOrDefault( fitting => ArchCabinetMounts.Same( fitting.Mount, mount ) );
	}

	// Falls back to Module slot if the requested slot has no art
	public ArchCabinetCarcass In( CabinetSlot slot ) {
		var standing = Carcasses?.FirstOrDefault( carcass => carcass.Slot == slot && carcass.Standing );

		if ( standing is not null || slot == CabinetSlot.Module ) {
			return standing;
		}

		return Carcasses?.FirstOrDefault( carcass => carcass.Slot == CabinetSlot.Module && carcass.Standing );
	}
}

public static class ArchCabinetTypes {
	public static List<ArchCabinetType> Shipped() {
		return new List<ArchCabinetType>
		{
			new()
			{
				Name = "short",
				Detail = "Base unit with a drawer over a door, worktop over",
				Tier = CabinetTier.Base,
				Width = 23.625f,
				Depth = 23.625f,
				Height = 28.375f,
				PlinthHeight = 5.5f,
				PlinthRecess = 2f,
				TopThickness = 1.5625f,
				DrawerRows = 1,
				DrawerHeight = 7.0625f,
				Shelves = 1,
				Fittings = new List<ArchCabinetFitting>
				{
					new() { Mount = ArchCabinetMounts.Front, Front = CabinetFront.Shaker },
					new() { Mount = ArchCabinetMounts.Drawer, Front = CabinetFront.Slab }
				}
			},
			new()
			{
				Name = "tall",
				Detail = "Larder, floor to soffit, front splitting where the wall units start",
				Tier = CabinetTier.Tall,
				Width = 23.625f,
				Depth = 23.625f,
				Height = 77.1875f,
				PlinthHeight = 5.5f,
				PlinthRecess = 2f,
				TopThickness = 0f,
				FrontSplit = 47.9375f,
				Shelves = 5,
				Fittings = new List<ArchCabinetFitting>
				{
					new() { Mount = ArchCabinetMounts.Front, Front = CabinetFront.Shaker },
					new() { Mount = ArchCabinetMounts.FrontUpper, Front = CabinetFront.Shaker }
				}
			},
			new()
			{
				Name = "hanging",
				Detail = "Wall unit, seated off the counter below it and rising to what covers it",
				Tier = CabinetTier.Hanging,
				Width = ArchCabinetType.NominalWidth,
				Depth = ArchCabinetType.HungDepth,
				Height = 28.375f,
				PlinthHeight = 0f,
				Datum = 53.4375f,
				TopThickness = 0f,
				Shelves = 2,
				Fittings = new List<ArchCabinetFitting>
				{
					new() { Mount = ArchCabinetMounts.Front, Front = CabinetFront.Shaker }
				}
			},
			new()
			{
				Name = "counter",
				Detail = "An island - panelled all round under a top with an even lip, flush plinth",
				Tier = CabinetTier.Base,
				Width = 23.625f,
				Depth = 23.625f,
				Height = 28.375f,
				PlinthHeight = 5.5f,
				TopThickness = 1.5625f,
				TopOversail = 1f,
				TopEndOversail = 1f,
				TopBackOversail = 1f,
				PlinthRecess = 0f,
				Shelves = 1,
				Fittings = new List<ArchCabinetFitting>
				{
					new() { Mount = ArchCabinetMounts.Front, Front = CabinetFront.Panel }
				}
			}
		};
	}

	public static ArchCabinetType Find( ArchKit kit, string name ) {
		var offered = kit?.Cabinets;

		if ( offered is null || offered.Count == 0 ) {
			return Shipped().FirstOrDefault();
		}

		return offered.FirstOrDefault( type => string.Equals( type.Name, name, StringComparison.OrdinalIgnoreCase ) )
			?? offered.FirstOrDefault();
	}

	public static ArchCabinetType Copy( ArchCabinetType type ) {
		if ( type is null ) {
			return Shipped().First();
		}

		return new ArchCabinetType {
			Name = type.Name,
			Detail = type.Detail,
			Tier = type.Tier,
			Width = type.Width,
			Depth = type.Depth,
			Height = type.Height,
			PlinthHeight = type.PlinthHeight,
			PlinthRecess = type.PlinthRecess,
			Datum = type.Datum,
			TopThickness = type.TopThickness,
			TopDepth = type.TopDepth,
			TopOversail = type.TopOversail,
			TopEndOversail = type.TopEndOversail,
			TopBackOversail = type.TopBackOversail,
			Corner = type.Corner,
			FrontSplit = type.FrontSplit,
			PanelThickness = type.PanelThickness,
			FrontThickness = type.FrontThickness,
			Reveal = type.Reveal,
			PanelInset = type.PanelInset,
			PanelRaise = type.PanelRaise,
			PanelMargin = type.PanelMargin,
			CorniceHeight = type.CorniceHeight,
			CorniceProjection = type.CorniceProjection,
			CorniceOversail = type.CorniceOversail,
			Shelves = type.Shelves,
			DrawerRows = type.DrawerRows,
			DrawerHeight = type.DrawerHeight,
			Handles = type.Handles,
			Grip = type.Grip,
			HandleLength = type.HandleLength,
			HandleThickness = type.HandleThickness,
			HandleStandoff = type.HandleStandoff,
			Interior = type.Interior,
			Prefabbed = type.Prefabbed,
			Hidden = type.Hidden,
			MaterialGroup = type.MaterialGroup,
			Fittings = type.Fittings?.Select( Copy ).ToList() ?? new List<ArchCabinetFitting>(),
			Carcasses = type.Carcasses?.Select( carcass => carcass.Copy() ).ToList() ?? new List<ArchCabinetCarcass>()
		};
	}

	static ArchCabinetFitting Copy( ArchCabinetFitting fitting ) {
		return new ArchCabinetFitting {
			Mount = fitting.Mount,
			Front = fitting.Front,
			Prefab = fitting.Prefab,
			Fits = fitting.Fits,
			Anchor = fitting.Anchor,
			AnchorAlong = fitting.AnchorAlong,
			AnchorTurn = fitting.AnchorTurn
		};
	}
}