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

namespace Sunless.Architecture;

// A RUN of carcasses along one wall, not a cabinet: the drag names a stretch of elevation and ArchDivide cuts it
// into modules, so the count is never typed and no sliver is left at either end.
//
// Stored as an anchor and a facing rather than a wall id, the way every other wall fixture is - a wall renumbered,
// thickened or dragged leaves the run standing where the author put it, and the resolve finds the wall again.
//
// Neither the seat nor the head is stored unless the author pinned one. A hanging run reaches for the counter
// below it and a tall one for whatever covers it, and both answers move when the thing they read moves.
public sealed class ArchCabinetPart : IArchCollides, IArchPainted, IArchNamed {
	public ArchCollisionMode? Collision { get; set; }

	public int Id { get; set; }
	public string Name { get; set; } = "Cabinets";
	public int Level { get; set; }

	public Vector2 Anchor { get; set; }
	// Out of the wall and into the room, which is the way the fronts look.
	public Vector2 Outward { get; set; } = new( 1f, 0f );
	public float Width { get; set; } = 96f;

	public CabinetTier Tier { get; set; } = CabinetTier.Auto;
	public string Type { get; set; } = "";

	// Every one of these is zero for "the type's own answer", so a run tuned on one wall says only what it
	// disagrees with and a retyped run picks the rest up.
	public float ModuleWidth { get; set; }
	public float Depth { get; set; }
	public float TopDepth { get; set; }
	public float Height { get; set; }

	public float BaseHeight { get; set; }
	// The author set that foot by hand and means it, which is the only thing that stops the seat deriving.
	public bool Seated { get; set; }

	public bool Interior { get; set; }

	// Null is the type's answer. Nullable rather than zero-means-inherit, because no shelves, no drawers, no
	// handles and no counter are all things an author means on purpose.
	public int? Shelves { get; set; }
	public int? DrawerRows { get; set; }
	public bool? Handles { get; set; }
	public float? TopThickness { get; set; }
	public float? TopEndOversail { get; set; }
	public float? TopBackOversail { get; set; }

	// Which group the art in this run wears, where the type is made of prefabs. Empty follows the type, so a row
	// is re-finished by naming one group here rather than by authoring a second type.
	public string MaterialGroup { get; set; } = "";

	public ArchPalette Palette { get; set; } = new();

	// Overrides for THIS run's mounts, on top of whatever the type says - one door swapped for a prefab without
	// authoring a type nobody else will use.
	public List<ArchCabinetFitting> Fittings { get; set; } = new();

	public Vector2 Facing => Outward.Length < 0.01f ? new Vector2( 1f, 0f ) : Outward.Normal;

	// ArchMesh.Beam takes its offsets along ArchRegion.Outward of the line it is given, so the run has to travel
	// the way that answers the facing - or every carcass comes out behind its own wall.
	public Vector2 Along {
		get {
			var facing = Facing;

			return new Vector2( -facing.y, facing.x );
		}
	}

	public float Run => MathF.Max( 6f, Width );

	// What the footprint stands out of the wall when the question is asked with no kit to reach the type through -
	// the layer tree and the hit stack both ask that way, so the TIER answers for the type they cannot see. A wall
	// unit read at base depth picks a box twice as deep as the carcass a click is aiming at.
	public float Reach => Depth > 1f
		? Depth
		: Tier == CabinetTier.Hanging ? ArchCabinetType.HungDepth : ArchCabinetType.NominalDepth;

	public Vector2 Start => Anchor - Along * (Run * 0.5f);

	public Vector2 End => Anchor + Along * (Run * 0.5f);

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