Editor/Geometry/ArchWallSection.cs
using System;
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

// What a wall MEASURES, resolved once and never re-derived at a caller: wall, then room, then kit. A handle, an
// elevation, a report, a damage bite and a fixture seat all need these numbers whether or not anything is installed
// to generate the wall itself.
public static class ArchWallSection {
	public static float Height( ArchWall wall, ArchRoom room, ArchKit kit ) => Resolve( wall.Height, room?.WallHeight ?? 0f, kit.WallHeight );

	public static float Thickness( ArchWall wall, ArchKit kit ) => Resolve( wall.Thickness, 0f, kit.WallThickness );

	// What a wall with no height of its own stands at - the ceiling a half wall is measured against.
	public static float CeilingHeight( ArchRoom room, ArchKit kit ) => Resolve( 0f, room?.WallHeight ?? 0f, kit.WallHeight );

	// Whether the far face exists at all. A facade room says it of every wall it holds, so a shell drawn to be
	// seen from one side stays one-sided as it is added to - the ONE answer, or a wall generator and the false
	// interior pass disagree about whether there is an inside to line.
	public static bool SingleSided( ArchWall wall, ArchRoom room ) => wall.SingleSided || room is { Facade: true };

	// Whether there is an inside to finish at all. A skirting, a wainscot and an interior moulding are all
	// geometry standing IN a room, so a wall with no far face has nowhere to put them - and the author's own
	// switches still have the last word where there is an inside.
	public static bool Lined( ArchWall wall, ArchRoom room ) => !SingleSided( wall, room );

	// One resolution of the coping section: generator, designer form and report read the same cap.
	public static float CapHeight( IArchWallTreatment wall, ArchKit kit ) => Resolve( wall.CapHeight, 0f, kit.WallCapHeight );

	public static float CapOverhang( IArchWallTreatment wall, ArchKit kit ) => Resolve( wall.CapOverhang, 0f, kit.CasingDepth );

	// A wall is a band, not a line: its centreline grown by its own thickness.
	public static List<Vector2> Band( ArchWall wall, ArchKit kit ) {
		var side = wall.Normal * (Thickness( wall, kit ) * 0.5f);

		return new List<Vector2> { wall.Start + side, wall.End + side, wall.End - side, wall.Start - side };
	}

	// The one resolution: generator and report agree on how many.
	public static IEnumerable<float> PilasterCentres( ArchPilasterRun run, float from, float to ) {
		var span = to - from;

		if ( run is null || !run.Stands || span < 1f ) {
			yield break;
		}

		var strip = MathF.Min( run.Width, span );
		var division = run.Count > 0 ? ArchDivide.Into( span, Math.Max( 1, run.Count - 1 ) ) : ArchDivide.AtMost( span, run.Bay );

		foreach ( var node in division.Nodes ) {
			yield return Math.Clamp( from + node, from + strip * 0.5f, to - strip * 0.5f );
		}
	}

	static float Resolve( float value, float roomValue, float kitValue ) {
		if ( value > 0f ) {
			return value;
		}

		return roomValue > 0f ? roomValue : kitValue;
	}
}