Editor/Cabinet/ArchCabinetShape.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

// Space is in mount frame: x out of face, y along run, z up.
public readonly record struct ArchCabinetMount( string Name, Transform Local, Vector3 Space );

public sealed class ArchCabinetCell {
	public int Index { get; init; }
	public Vector2 Start { get; init; }
	public Vector2 End { get; init; }
	public float Foot { get; init; }
	public float Head { get; init; }
	public float Depth { get; init; }
	public CabinetSlot Slot { get; init; }
	public List<ArchCabinetMount> Mounts { get; } = new();

	public float Width => (End - Start).Length;

	public ArchCabinetMount? Mount( string name ) {
		foreach ( var mount in Mounts ) {
			if ( ArchCabinetMounts.Same( mount.Name, name ) ) {
				return mount;
			}
		}

		return null;
	}
}

readonly record struct ArchCabinetSquare(
	Vector2 Corner, Vector2 Along, Vector2 Across, float LegAlong, float LegAcross, float DepthAlong,
	float TopAlong, float BackAlong );

sealed class ArchCabinetEnd {
	public float Carcass { get; private set; }

	public float Counter { get; private set; }

	public ArchCabinetSquare? Square { get; private set; }

	public bool Standing => Carcass > 0.01f;

	public void Deeper( float carcass, float counter, ArchCabinetSquare? square ) {
		if ( carcass <= Carcass ) {
			return;
		}

		Carcass = carcass;
		Counter = counter;
		Square = square;
	}
}

public sealed class ArchCabinetJoint {
	public int Index { get; init; }
	public List<Vector2> Loop { get; init; } = new();
	public List<Vector2> Face { get; init; } = new();
	public List<Vector2> Kick { get; init; } = new();
	public List<Vector2> Top { get; init; } = new();
	public List<Vector2> Crown { get; init; } = new();
	public float Foot { get; init; }
	public float Head { get; init; }
	public float Seat { get; init; }
	public float Plinth { get; init; }
	public float Counter { get; init; }
	public float Cornice { get; init; }
	public List<ArchCabinetMount> Mounts { get; } = new();

	public ArchCabinetMount? Mount( string name ) {
		foreach ( var mount in Mounts ) {
			if ( ArchCabinetMounts.Same( mount.Name, name ) ) {
				return mount;
			}
		}

		return null;
	}
}

public sealed class ArchCabinetShape {
	public const float Splashback = 18f;

	public const float LeastHead = 12f;

	public const float LeastRun = 6f;

	public const float HungDatum = 53.4375f;

	public ArchCabinetType Type { get; init; }
	public CabinetTier Tier { get; init; }
	public Vector2 Start { get; init; }
	public Vector2 End { get; init; }
	public Vector2 Along { get; init; }
	public Vector2 Facing { get; init; }
	public float Seat { get; init; }
	public float Head { get; init; }
	public float Depth { get; init; }
	public float TopDepth { get; init; }
	public int Shelves { get; init; }
	public int DrawerRows { get; init; }
	public float DrawerHeight { get; init; }
	public bool Handles { get; init; }
	public float Thickness { get; init; }
	public float EndOversail { get; init; }
	public float BackOversail { get; init; }
	public bool StartsOpen { get; init; }
	public bool EndsOpen { get; init; }
	public float CrownFrom { get; init; }
	public float CrownTo { get; init; }
	public float TopFrom { get; init; }
	public float TopTo { get; init; }
	public List<ArchCabinetCell> Cells { get; } = new();
	public List<ArchCabinetJoint> Joints { get; } = new();

	public bool Standing => Type is not null && (Cells.Count > 0 || Joints.Count > 0);

	public float Run => (End - Start).Length;

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

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

	public float Cornice { get; init; }

	public float CorniceDepth => Cornice <= 0.01f ? Depth : Depth + MathF.Max( 0f, Type?.CorniceProjection ?? 0f );

	public float CarcassFoot => Seat + Plinth;

	public float CarcassHead => MathF.Max( CarcassFoot + LeastHead, Head - Counter - Cornice );

	public List<Vector2> Outline() {
		var near = Facing * 0f;
		var far = Facing * MathF.Max( MathF.Max( Depth, TopDepth ), CorniceDepth );

		return new List<Vector2> { Start + near, End + near, End + far, Start + far };
	}

	public static ArchCabinetShape Resolve( ArchPlan plan, ArchKit kit, ArchRoom room, ArchBuilding building, ArchCabinetPart part ) {
		if ( part is null ) {
			return new ArchCabinetShape();
		}

		var type = ArchCabinetTypes.Find( kit, part.Type );

		if ( type is null ) {
			return new ArchCabinetShape();
		}

		var tier = part.Tier == CabinetTier.Auto ? type.Tier : part.Tier;
		var depth = Deep( part, type );
		var top = Topped( part, type, depth );
		var floor = room?.BaseHeight ?? 0f;
		var seat = Seated( plan, kit, room, part, type, tier, floor );
		var head = Reached( plan, kit, part, type, tier, seat, depth );
		var module = Module( part, type );

		Corners( plan, kit, part, type, depth, seat, head, module, out var first, out var last );

		var along = part.Along;
		var start = part.Start + along * first.Carcass;
		var run = part.Run - first.Carcass - last.Carcass;

		var stands = run >= LeastRun;
		var oversail = MathF.Max( 0f, part.TopEndOversail ?? type.TopEndOversail );
		var thickness = MathF.Max( 0f, part.TopThickness ?? type.TopThickness );
		var counter = tier == CabinetTier.Hanging ? 0f : thickness;
		var crown = counter > 0.01f ? 0f : MathF.Max( 0f, type.CorniceHeight );
		var crowning = crown <= 0.01f ? 0f : MathF.Max( 0f, type.CorniceOversail );

		var shape = new ArchCabinetShape {
			Type = type,
			Tier = tier,
			Start = start,
			End = start + along * (stands ? run : 0f),
			Along = along,
			Facing = part.Facing,
			Seat = seat,
			Head = head,
			Depth = depth,
			TopDepth = tier == CabinetTier.Hanging ? depth : top,
			Shelves = Math.Max( 0, part.Shelves ?? type.Shelves ),
			DrawerRows = Math.Max( 0, part.DrawerRows ?? type.DrawerRows ),
			DrawerHeight = MathF.Max( 2f, type.DrawerHeight ),
			Handles = part.Handles ?? type.Handles,
			Thickness = thickness,
			Cornice = crown,
			EndOversail = oversail,
			BackOversail = MathF.Max( 0f, part.TopBackOversail ?? type.TopBackOversail ),
			StartsOpen = !first.Standing,
			EndsOpen = !last.Standing,
			CrownFrom = first.Standing ? 0f : -crowning,
			CrownTo = last.Standing ? run : run + crowning,
			TopFrom = first.Standing ? first.Counter - first.Carcass : -oversail,
			TopTo = last.Standing ? run - (last.Counter - last.Carcass) : run + oversail
		};

		if ( stands ) {
			Fill( shape, type, module );
		}

		Join( shape, type, first.Square );
		Join( shape, type, last.Square );

		return shape;
	}

	static void Join( ArchCabinetShape shape, ArchCabinetType type, ArchCabinetSquare? held ) {
		if ( held is not { } square ) {
			return;
		}

		var recess = MathF.Max( 0f, type.PlinthRecess );
		var front = MathF.Max( 0.25f, type.FrontThickness );
		var crown = shape.Cornice <= 0.01f ? 0f : MathF.Max( 0f, type.CorniceProjection );
		var joint = new ArchCabinetJoint {
			Index = shape.Joints.Count,
			Loop = Pentagon( square, 0f, 0f, MathF.Max( 1f, square.DepthAlong - front ),
				MathF.Max( 1f, shape.Depth - front ) ),
			Face = Pentagon( square, 0f, 0f, square.DepthAlong, shape.Depth ),
			Kick = Pentagon( square, 0f, 0f, MathF.Max( 1f, square.DepthAlong - recess ),
				MathF.Max( 1f, shape.Depth - recess ) ),
			Top = Pentagon( square, -square.BackAlong, -shape.BackOversail, square.TopAlong, shape.TopDepth ),
			Crown = Pentagon( square, 0f, 0f, square.DepthAlong + crown, shape.Depth + crown ),
			Foot = shape.CarcassFoot,
			Head = shape.CarcassHead,
			Seat = shape.Seat,
			Plinth = shape.Plinth,
			Counter = shape.Counter,
			Cornice = shape.Cornice
		};

		Angled( shape, joint, square, type );
		shape.Joints.Add( joint );
	}

	static List<Vector2> Pentagon( ArchCabinetSquare square, float backAlong, float backAcross, float frontAlong,
		float frontAcross ) {
		var origin = square.Corner;
		var along = square.Along;
		var across = square.Across;

		return new List<Vector2>
		{
			origin + along * backAlong + across * backAcross,
			origin + along * square.LegAlong + across * backAcross,
			origin + along * square.LegAlong + across * frontAcross,
			origin + along * frontAlong + across * square.LegAcross,
			origin + along * backAlong + across * square.LegAcross
		};
	}

	static void Angled( ArchCabinetShape shape, ArchCabinetJoint joint, ArchCabinetSquare square, ArchCabinetType type ) {
		var facing = (square.Along + square.Across).Normal;
		var left = new Vector2( -facing.y, facing.x );
		var one = joint.Face[2];
		var other = joint.Face[3];
		var from = Vector2.Dot( other - one, left ) > 0f ? one : other;
		var span = (other - one).Length;
		var thickness = MathF.Max( 0.25f, type.FrontThickness );
		var width = span - type.Reveal * 2f;

		if ( type.Prefabbed ) {
			joint.Mounts.Add( Angle( from, Rotation.LookAt( new Vector3( facing.x, facing.y, 0f ), Vector3.Up ),
				ArchCabinetMounts.CornerCarcass, shape.Head, shape.Depth, span, shape.Head - shape.Seat ) );

			return;
		}
		var drawer = shape.DrawerRows > 0 ? shape.DrawerHeight * shape.DrawerRows : 0f;
		var opening = joint.Head - joint.Foot - drawer;

		if ( width < 1f || opening < 1f ) {
			return;
		}

		var seat = from - facing * thickness + left * type.Reveal;
		var rotation = Rotation.LookAt( new Vector3( facing.x, facing.y, 0f ), Vector3.Up );

		if ( drawer > 0.5f ) {
			joint.Mounts.Add( Angle( seat, rotation, ArchCabinetMounts.CornerDrawer, joint.Head, thickness, width,
				drawer - type.Reveal * 2f ) );
		}

		if ( type.Splits && type.FrontSplit < opening ) {
			var split = joint.Foot + type.FrontSplit;

			joint.Mounts.Add( Angle( seat, rotation, ArchCabinetMounts.CornerFront, split, thickness, width,
				split - joint.Foot - type.Reveal * 2f ) );
			joint.Mounts.Add( Angle( seat, rotation, ArchCabinetMounts.CornerFrontUpper, joint.Head - drawer, thickness,
				width, joint.Head - drawer - split - type.Reveal * 2f ) );
		} else {
			joint.Mounts.Add( Angle( seat, rotation, ArchCabinetMounts.CornerFront, joint.Head - drawer, thickness,
				width, opening - type.Reveal * 2f ) );
		}

		Grips( shape, joint, type );
	}

	static ArchCabinetMount Angle( Vector2 seat, Rotation rotation, string name, float top, float thickness,
		float width, float height ) {
		return new ArchCabinetMount( name, new Transform( new Vector3( seat.x, seat.y, top ), rotation ),
			new Vector3( thickness, width, MathF.Max( 0f, height ) ) );
	}

	static void Grips( ArchCabinetShape shape, ArchCabinetJoint joint, ArchCabinetType type ) {
		if ( !shape.Handles ) {
			return;
		}

		foreach ( var (face, grip) in new[]
		{
			(ArchCabinetMounts.CornerFront, ArchCabinetMounts.CornerHandle),
			(ArchCabinetMounts.CornerFrontUpper, ArchCabinetMounts.CornerHandleUpper)
		} ) {
			if ( joint.Mount( face ) is not { } front ) {
				continue;
			}

			var reach = MathF.Min( MathF.Max( 2f, type.HandleLength ), front.Space.y * 0.5f );
			var down = MathF.Min( 3f, front.Space.z * 0.5f );
			var local = front.Local.Position + front.Local.Rotation.Forward * front.Space.x
				+ front.Local.Rotation.Left * (front.Space.y - reach - 2f) + Vector3.Down * down;

			joint.Mounts.Add( new ArchCabinetMount( grip, new Transform( local, front.Local.Rotation ),
				new Vector3( MathF.Max( 0.2f, type.HandleStandoff ), reach, MathF.Max( 0.2f, type.HandleThickness ) ) ) );
		}
	}

	// Module width is a ceiling — runs are filled with standard carcasses plus one narrower filler.
	static void Fill( ArchCabinetShape shape, ArchCabinetType type, float module ) {
		var division = ArchDivide.AtMost( shape.Run, module );

		for ( var index = 0; index < division.Count; index++ ) {
			var (from, to) = division.Bay( index );

			var cell = new ArchCabinetCell {
				Index = index,
				Start = shape.Start + shape.Along * from,
				End = shape.Start + shape.Along * to,
				Foot = shape.CarcassFoot,
				Head = shape.CarcassHead,
				Depth = shape.Depth,
				Slot = Slotted( shape, index, division.Count, to - from, module )
			};

			Station( shape, cell, type );
			shape.Cells.Add( cell );
		}
	}

	static CabinetSlot Slotted( ArchCabinetShape shape, int index, int count, float width, float module ) {
		if ( width < module - 0.5f ) {
			return CabinetSlot.Filler;
		}

		if ( index == count - 1 && shape.EndsOpen ) {
			return CabinetSlot.End;
		}

		return index == 0 && shape.StartsOpen ? CabinetSlot.End : CabinetSlot.Module;
	}

	static void Station( ArchCabinetShape shape, ArchCabinetCell cell, ArchCabinetType type ) {
		if ( type.Prefabbed ) {
			cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Carcass, 0f, shape.Head, 0f, cell.Width,
				shape.Head - shape.Seat, shape.Depth ) );

			return;
		}

		var face = shape.Depth - type.FrontThickness;
		var width = cell.Width - type.Reveal * 2f;
		var drawer = shape.DrawerRows > 0 ? shape.DrawerHeight * shape.DrawerRows : 0f;
		var opening = cell.Head - cell.Foot - drawer;

		if ( drawer > 0.5f ) {
			cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Drawer, face, cell.Head, type.Reveal, width,
				drawer - type.Reveal * 2f, type.FrontThickness ) );
		}

		if ( type.Splits && type.FrontSplit < opening ) {
			var split = cell.Foot + type.FrontSplit;

			cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Front, face, split, type.Reveal, width,
				split - cell.Foot - type.Reveal * 2f, type.FrontThickness ) );
			cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.FrontUpper, face, cell.Head - drawer, type.Reveal,
				width, cell.Head - drawer - split - type.Reveal * 2f, type.FrontThickness ) );
		} else {
			cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Front, face, cell.Head - drawer, type.Reveal, width,
				opening - type.Reveal * 2f, type.FrontThickness ) );
		}

		Worktop( shape, cell );

		if ( shape.Plinth > 0.01f ) {
			cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Plinth, 0f, cell.Foot, 0f,
				cell.Width, shape.Plinth, MathF.Max( 1f, shape.Depth - type.PlinthRecess ) ) );
		}

		Crowning( shape, cell );
		Grip( shape, cell, type );
	}

	static void Worktop( ArchCabinetShape shape, ArchCabinetCell cell ) {
		if ( shape.Counter <= 0.01f ) {
			return;
		}

		var at = Vector2.Dot( cell.Start - shape.Start, shape.Along );
		var opens = cell.Index == 0 ? shape.TopFrom : at;
		var closes = at + cell.Width > shape.Run - 0.05f ? shape.TopTo : at + cell.Width;
		var from = MathF.Max( shape.TopFrom, opens ) - at;
		var to = MathF.Min( shape.TopTo, closes ) - at;

		if ( to - from < 0.05f ) {
			return;
		}

		cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Worktop, -shape.BackOversail, shape.Head, from,
			to - from, shape.Counter, shape.TopDepth + shape.BackOversail ) );
	}

	static void Crowning( ArchCabinetShape shape, ArchCabinetCell cell ) {
		if ( shape.Cornice <= 0.01f ) {
			return;
		}

		var at = Vector2.Dot( cell.Start - shape.Start, shape.Along );
		var from = (cell.Index == 0 ? shape.CrownFrom : at) - at;
		var to = (at + cell.Width > shape.Run - 0.05f ? shape.CrownTo : at + cell.Width) - at;

		if ( to - from < 0.05f ) {
			return;
		}

		cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Cornice, 0f, cell.Head + shape.Cornice, from,
			to - from, shape.Cornice, shape.CorniceDepth ) );
	}

	static void Grip( ArchCabinetShape shape, ArchCabinetCell cell, ArchCabinetType type ) {
		if ( !shape.Handles ) {
			return;
		}

		foreach ( var name in new[] { ArchCabinetMounts.Front, ArchCabinetMounts.FrontUpper } ) {
			if ( cell.Mount( name ) is not { } front ) {
				continue;
			}

			var grip = ArchCabinetMounts.Same( name, ArchCabinetMounts.Front ) ? ArchCabinetMounts.Handle : ArchCabinetMounts.HandleUpper;
			var reach = MathF.Min( MathF.Max( 2f, type.HandleLength ), front.Space.y * 0.5f );
			var down = MathF.Min( 3f, front.Space.z * 0.5f );
			var local = front.Local.Position + front.Local.Rotation.Forward * front.Space.x
				+ front.Local.Rotation.Left * (front.Space.y - reach - 2f) + Vector3.Down * down;

			cell.Mounts.Add( new ArchCabinetMount( grip, new Transform( local, front.Local.Rotation ),
				new Vector3( MathF.Max( 0.2f, type.HandleStandoff ), reach, MathF.Max( 0.2f, type.HandleThickness ) ) ) );
		}
	}

	// Origin at piece TOP, -x -y corner; fills +x(depth) +y(width) -z(height).
	static ArchCabinetMount Mount( ArchCabinetShape shape, ArchCabinetCell cell, string name, float back, float top,
		float along, float width, float height, float depth ) {
		var corner = cell.Start + shape.Facing * back + shape.Along * along;
		var rotation = Rotation.LookAt( new Vector3( shape.Facing.x, shape.Facing.y, 0f ), Vector3.Up );

		return new ArchCabinetMount( name, new Transform( new Vector3( corner.x, corner.y, top ), rotation ),
			new Vector3( MathF.Max( 0f, depth ), MathF.Max( 0f, width ), MathF.Max( 0f, height ) ) );
	}

	static float Seated( ArchPlan plan, ArchKit kit, ArchRoom room, ArchCabinetPart part, ArchCabinetType type,
		CabinetTier tier, float floor ) {
		if ( part.Seated ) {
			return part.BaseHeight;
		}

		if ( tier != CabinetTier.Hanging ) {
			return floor;
		}

		if ( CounterBelow( plan, kit, part ) is { } counter ) {
			return counter + Splashback;
		}

		return floor + (type.Datum > 1f ? type.Datum : HungDatum);
	}

	static float Reached( ArchPlan plan, ArchKit kit, ArchCabinetPart part, ArchCabinetType type, CabinetTier tier,
		float seat, float depth ) {
		if ( part.Height > 1f ) {
			return seat + part.Height;
		}

		var stood = seat + Stood( part, type, tier );

		if ( tier == CabinetTier.Base ) {
			return stood;
		}

		var under = part.Anchor + part.Facing * (depth * 0.5f);
		var soffit = ArchPillarSoffit.Covering( plan, kit, under, seat );

		if ( soffit >= float.MaxValue ) {
			return stood;
		}

		return tier == CabinetTier.Tall
			? MathF.Max( seat + LeastHead, soffit )
			: MathF.Min( stood, MathF.Max( seat + LeastHead, soffit ) );
	}

	static float Stood( ArchCabinetPart part, ArchCabinetType type, CabinetTier tier ) {
		var plinth = tier == CabinetTier.Hanging ? 0f : MathF.Max( 0f, type.PlinthHeight );
		var counter = tier == CabinetTier.Hanging ? 0f : MathF.Max( 0f, part.TopThickness ?? type.TopThickness );

		return plinth + type.Carcass + counter;
	}

	static float Deep( ArchCabinetPart part, ArchCabinetType type ) {
		return part.Depth > 1f ? part.Depth : MathF.Max( 4f, type.Depth );
	}

	static float Topped( ArchCabinetPart part, ArchCabinetType type, float depth ) {
		if ( part.TopDepth > 1f ) {
			return part.TopDepth;
		}

		return type.TopDepth > 1f ? type.TopDepth : depth + MathF.Max( 0f, type.TopOversail );
	}

	static float Module( ArchCabinetPart part, ArchCabinetType type ) {
		return part.ModuleWidth > 1f ? part.ModuleWidth : MathF.Max( 6f, type.Width );
	}

	static bool Tops( ArchCabinetPart part, ArchCabinetType type, CabinetTier tier ) {
		return tier != CabinetTier.Hanging && (part.TopThickness ?? type.TopThickness) > 0.01f;
	}

	// Lower id keeps the corner; drafts (id <= 0) always keep.
	static bool Keeps( ArchCabinetPart one, ArchCabinetPart other ) {
		if ( other.Id <= 0 ) {
			return false;
		}

		return one.Id <= 0 || one.Id < other.Id;
	}

	static void Corners( ArchPlan plan, ArchKit kit, ArchCabinetPart part, ArchCabinetType own, float depth,
		float seat, float head, float module, out ArchCabinetEnd first, out ArchCabinetEnd last ) {
		first = new ArchCabinetEnd();
		last = new ArchCabinetEnd();

		if ( plan is null ) {
			return;
		}

		var origin = part.Start;
		var along = part.Along;
		var facing = part.Facing;
		var run = part.Run;

		foreach ( var standing in plan.Parts<ArchCabinetPart>() ) {
			if ( standing.Id == part.Id || standing.Level != part.Level || !ArchLayerGate.On( standing ) ) {
				continue;
			}

			if ( MathF.Abs( Vector2.Dot( standing.Facing, facing ) ) > 0.1f ) {
				continue;
			}

			var type = ArchCabinetTypes.Find( kit, standing.Type );

			if ( type is null ) {
				continue;
			}

			var tier = standing.Tier == CabinetTier.Auto ? type.Tier : standing.Tier;

			if ( !Shares( plan, standing, type, tier, seat, head ) || !Beside( standing, origin, facing, depth ) ) {
				continue;
			}

			var reach = Deep( standing, type );
			var across = Vector2.Dot( along, standing.Facing );
			var offset = Vector2.Dot( origin - standing.Start, standing.Facing );
			var back = -offset / across;

			var atFirst = MathF.Abs( back ) < module;
			var atLast = MathF.Abs( back - run ) < module;

			if ( !atFirst && !atLast ) {
				continue;
			}

			var corner = origin + along * back;

			if ( !Ends( standing, corner, module ) ) {
				continue;
			}

			var keeper = Keeps( standing, part ) ? type : own;

			if ( keeper.Corner == CabinetCorner.None ) {
				continue;
			}

			var front = (reach - offset) / across;
			var lip = Tops( standing, type, tier ) ? (Topped( standing, type, reach ) - offset) / across : front;

			if ( keeper.Corner == CabinetCorner.Blind ) {
				Blind( first, last, back, front, lip, run, atFirst, Keeps( part, standing ) );

				continue;
			}

			var splay = MathF.Max( depth, reach ) * 0.5f;
			var mine = reach + splay;
			var theirs = depth + splay;
			var owns = Keeps( part, standing );
			var square = new ArchCabinetSquare( corner, atFirst ? along : -along, facing, mine, theirs, reach,
				Tops( standing, type, tier ) ? Topped( standing, type, reach ) : reach,
				MathF.Max( 0f, standing.TopBackOversail ?? type.TopBackOversail ) );

			if ( atFirst ) {
				first.Deeper( back + mine, back + mine, owns ? square : null );
			} else {
				last.Deeper( run - (back - mine), run - (back - mine), owns ? square : null );
			}
		}
	}

	static void Blind( ArchCabinetEnd first, ArchCabinetEnd last, float back, float front, float lip, float run,
		bool atFirst, bool keeps ) {
		if ( keeps ) {
			return;
		}

		var near = MathF.Min( back, front );
		var far = MathF.Max( back, front );

		if ( atFirst && far > 0.05f ) {
			first.Deeper( far, MathF.Max( far, MathF.Max( back, lip ) ), null );
		}

		if ( !atFirst && near < run - 0.05f ) {
			last.Deeper( run - near, run - MathF.Min( near, MathF.Min( back, lip ) ), null );
		}
	}

	static bool Ends( ArchCabinetPart standing, Vector2 corner, float module ) {
		var at = Vector2.Dot( corner - standing.Start, standing.Along );

		return MathF.Abs( at ) < module || MathF.Abs( at - standing.Run ) < module;
	}

	static bool Beside( ArchCabinetPart standing, Vector2 origin, Vector2 facing, float depth ) {
		var one = Vector2.Dot( standing.Start - origin, facing );
		var other = Vector2.Dot( standing.End - origin, facing );

		return MathF.Min( one, other ) < depth - 0.05f && MathF.Max( one, other ) > 0.05f;
	}

	// Answered without resolving the neighbour — Resolve calls Corners which calls back here.
	static bool Shares( ArchPlan plan, ArchCabinetPart standing, ArchCabinetType type, CabinetTier tier, float seat,
		float head ) {
		var floor = standing.Seated ? standing.BaseHeight : Floor( plan, standing );
		var foot = !standing.Seated && tier == CabinetTier.Hanging
			? floor + (type.Datum > 1f ? type.Datum : HungDatum)
			: floor;
		var over = standing.Height > 1f ? foot + standing.Height : foot + Stood( standing, type, tier );

		return foot < head - 0.5f && over > seat + 0.5f;
	}

	static float? CounterBelow( ArchPlan plan, ArchKit kit, ArchCabinetPart part ) {
		if ( plan is null ) {
			return null;
		}

		var found = (float?)null;

		foreach ( var standing in plan.Parts<ArchCabinetPart>() ) {
			if ( standing.Id == part.Id || standing.Level != part.Level || !ArchLayerGate.On( standing ) ) {
				continue;
			}

			if ( Vector2.Dot( standing.Facing, part.Facing ) < 0.9f || !Overlaps( standing, part ) ) {
				continue;
			}

			var type = ArchCabinetTypes.Find( kit, standing.Type );
			var tier = standing.Tier == CabinetTier.Auto ? type?.Tier ?? CabinetTier.Base : standing.Tier;

			if ( type is null || tier != CabinetTier.Base || (standing.TopThickness ?? type.TopThickness) < 0.01f ) {
				continue;
			}

			var seat = standing.Seated ? standing.BaseHeight : Floor( plan, standing );
			var top = standing.Height > 1f ? seat + standing.Height : seat + Stood( standing, type, tier );

			if ( found is null || top > found ) {
				found = top;
			}
		}

		return found;
	}

	static float Floor( ArchPlan plan, ArchCabinetPart part ) {
		return plan?.HostOf<ArchRoom>( part )?.BaseHeight ?? 0f;
	}

	static bool Overlaps( ArchCabinetPart one, ArchCabinetPart other ) {
		var along = other.Along;
		var mine = Vector2.Dot( one.Anchor - other.Anchor, along );
		var reach = (one.Run + other.Run) * 0.5f;

		var across = Vector2.Dot( one.Anchor - other.Anchor, other.Facing );

		return MathF.Abs( mine ) < reach && MathF.Abs( across ) < 24f;
	}
}