Editor/Carve/ArchCutGen.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

// The positive half: hole derived every rebuild, this builds the shaft walls and the housing.
public static class ArchCutGen {
	// Takes the HOST's palette, not the host: a chamber cut off a bore is skinned by the road that holds it, and
	// a shaft by its building, through one call.
	public static void Build( ArchMesh canvas, ArchCutPart cut, ArchPalette owner, ArchKit kit, ArchStyle style ) {
		if ( cut is not { HasContent: true, Builds: true } ) {
			return;
		}

		var chain = new[] { cut.Palette, owner ?? new ArchPalette() };

		if ( cut.Enclosure == CutEnclosure.Walls ) {
			using ( canvas.Part( ArchPieces.Enclosure ) ) {
				Enclosure( canvas, cut, style.Brush( ArchSurface.WallExterior, chain ) );
			}
		}

		if ( cut.Head != CutHead.None ) {
			using ( canvas.Part( ArchPieces.Capping ) ) {
				Head( canvas, cut, kit, style, chain );
			}
		}
	}

	// Per leg, not the chain - the legs climb, so the shaft is a stack following the stair.
	static void Enclosure( ArchMesh canvas, ArchCutPart cut, ArchBrush brush ) {
		var thickness = MathF.Max( 1f, cut.WallThickness );

		foreach ( var leg in cut.Legs() ) {
			ArchCarveRing.Walls( canvas, leg.Outline(), thickness, leg.BaseHeight, leg.TopHeight, brush );
		}
	}

	// Last leg's footprint (it reached the roof), standing on that leg's head, not a typed height.
	static void Head( ArchMesh canvas, ArchCutPart cut, ArchKit kit, ArchStyle style, ArchPalette[] chain ) {
		if ( cut.Legs().LastOrDefault() is not { } top ) {
			return;
		}

		var thickness = MathF.Max( 1f, cut.WallThickness );
		var outline = top.Outline();
		var foot = top.TopHeight;

		if ( cut.Head == CutHead.Housing ) {
			var head = foot + MathF.Max( 12f, cut.HeadHeight );

			ArchCarveRing.Walls( canvas, outline, thickness, foot, head, style.Brush( ArchSurface.WallExterior, chain ) );

			if ( cut.HeadRoof ) {
				Lid( canvas, outline, thickness, head, MathF.Max( 1f, kit.FloorThickness ), style.Brush( ArchSurface.Roof, chain ) );
			}

			return;
		}

		if ( cut.Head == CutHead.Coping ) {
			Coping( canvas, cut, top, thickness, style.Brush( ArchSurface.WallCap, chain ), kit );

			return;
		}

		Lid( canvas, outline, thickness, foot, MathF.Max( 1f, kit.FloorThickness ), style.Brush( ArchSurface.Roof, chain ) );
	}

	// The mouth's own band, walked round the leg's loop rather than round a grown one, so it stands over the
	// enclosure wall and oversails INTO the hole. Same section a platform's outside edge wears, bitten into the
	// head it stands on for the same reason: sitting on it puts three faces on every edge.
	static void Coping( ArchMesh canvas, ArchCutPart cut, ArchCutSegment top, float thickness, ArchBrush brush, ArchKit kit ) {
		var head = top.TopHeight;

		var band = ArchBandSection.Between(
			-MathF.Max( 0f, cut.CopingOversail ),
			thickness,
			head - ArchLap.Bite( kit ),
			head + MathF.Max( 1f, cut.CopingHeight ) );

		ArchRun.Around( ArchFootprint.Wind( top.Outline() ) ).Level( head ).Emit( canvas, band, brush );
	}


	static void Lid( ArchMesh canvas, IReadOnlyList<Vector2> outline, float thickness, float bottom, float depth, ArchBrush brush ) {
		var grown = ArchFootprint.Grow( new List<List<Vector2>> { outline.ToList() }, thickness ).First();

		ArchFloorGen.Slab( canvas, grown, null, bottom, bottom + depth, brush );
	}
}