Editor/Floor/ArchFloorGen.Foundation.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Sandbox;

namespace Sunless.Architecture;

public static partial class ArchFloorGen {
	public static IArchCarveHost StandsOn( ArchPlan plan, ArchBuilding building ) {
		if ( plan is null || building is null ) {
			return null;
		}

		// Shell, not centroid: a wing off the pad's edge can't drag the building off it.
		var shell = building.Rooms.FirstOrDefault( room => room.Floor == 0 && room.HasFootprint );

		if ( shell is null ) {
			return null;
		}

		var centre = Vector2.Zero;

		foreach ( var point in shell.Footprint ) {
			centre += point;
		}

		return ArchCarveHosts.Under( plan, 0, centre / shell.Footprint.Count );
	}

	// Derived from rooms, never stored: a saved lift goes stale the moment the foundation switches off. Held for
	// the build, because every part of a building rides its lift and finding the pad walks the plan.
	public static float GradeLift( ArchPlan plan, ArchBuilding building, ArchKit kit ) {
		return ArchBuildMemo.Held( memo => memo.Lifts, building?.Id ?? 0, () => Graded( plan, building, kit ) );
	}

	static float Graded( ArchPlan plan, ArchBuilding building, ArchKit kit ) {
		if ( building is null ) {
			return 0f;
		}

		if ( StandsOn( plan, building ) is { } pad ) {
			return MathF.Max( 0f, pad.TopHeight );
		}

		var raised = building.Rooms.Any( room => room.Floor == 0 && room.HasFloor && room.RaisedFoundation );

		return raised ? MathF.Max( 0f, kit.FoundationRise ) : 0f;
	}

	// HOW FAR A PLINTH STANDS PROUD of the footprint it carries: half a wall to clear the elevation, then the kit's
	// own oversail. ONE answer, because the generator pours the plinth on it and the snap offers its edge - worked
	// out twice, the corner an author can plainly see standing on the plinth is not a corner the cursor can reach,
	// and a column aimed at it comes to rest on the wall face a whole oversail inside it.
	public static float FoundationReach( ArchKit kit ) {
		return MathF.Max( 0f, kit?.WallThickness ?? 0f ) * 0.5f + MathF.Max( 0f, kit?.FoundationOversize ?? 0f );
	}

	public static int FoundationLevel( ArchBuilding building ) {
		return building?.Rooms
			.Where( room => room.HasFloor && ArchLayerGate.On( room ) )
			.Select( room => room.Floor )
			.DefaultIfEmpty( 0 )
			.Min() ?? 0;
	}

	// Whether this storey stands a plinth at all - the same gate the generator opens on, asked without a group.
	public static bool Founded( ArchPlan plan, ArchBuilding building, ArchKit kit, int level ) {
		return level == FoundationLevel( building )
			&& MathF.Max( 0f, kit?.FoundationDepth ?? 0f ) >= 1f
			&& building?.Rooms.Any( room => room.Floor == level && room.HasFloor && ArchLayerGate.On( room ) ) == true
			&& (level < 0 || StandsOn( plan, building ) is null);
	}

	// The plinth's OUTER RING. The generator breaks it at every approach mouth and every well after this; the ring
	// and the reach are what anything asking where its edge runs needs, and both come from here.
	public static List<List<Vector2>> FoundationOutline( ArchPlan plan, ArchBuilding building, ArchKit kit, int level ) {
		if ( !Founded( plan, building, kit, level ) ) {
			return new List<List<Vector2>>();
		}

		var storey = ArchRegion.Storey( building, level );

		return storey.Count == 0 ? new List<List<Vector2>>() : ArchFootprint.Grow( storey, FoundationReach( kit ) );
	}

	public static void Foundation( ArchMesh canvas, ArchRoom room, ArchBuilding building, ArchKit kit, ArchStyle style, ArchPlan plan = null ) {
		var group = Group( new List<ArchRoom> { room }, FloorKey( room, building, style ), kit );

		if ( group is not null ) {
			Foundation( canvas, group, building, kit, style, plan );
		}
	}

	// ONE solid: a masonry ring decomposes to coplanar face pairs at every corner.
	public static void Foundation( ArchMesh canvas, ArchFloorGroup group, ArchBuilding building, ArchKit kit, ArchStyle style, ArchPlan plan = null ) {
		var lead = group.Lead;
		var depth = MathF.Max( 0f, kit.FoundationDepth );

		// A pad is already the plinth: a second footing shows where the coping is shallow.
		if ( group.Level != FoundationLevel( building ) || !lead.HasFloor || depth < 1f
			|| (group.Level >= 0 && StandsOn( plan, building ) is not null) ) {
			return;
		}

		var brush = style.Brush( ArchSurface.Foundation, new[] { lead.Palette, building.Palette } );
		var bite = ArchLap.Bite( kit );
		var soffit = lead.BaseHeight - kit.FloorThickness;
		var bottom = soffit + bite - depth;
		var footprint = group.Region;

		// A hole through the slab has to go through what the slab STANDS ON, or the plinth fills it from
		// underneath and the floor is only open when you look at it from above. It takes the slab's own
		// holes rather than its own band's: a hole that stops on the footing is a pit, not a hole.
		var wells = Wells( plan, group, building, kit, soffit, lead.BaseHeight );

		// Raised foundation: the plinth runs past the slab's edge to just under the finished floor.
		var top = lead.RaisedFoundation ? lead.BaseHeight + bite : soffit + bite;

		var oversize = FoundationReach( kit );

		// The plinth breaks over each mouth: the approach IS the surface across that threshold.
		var kinds = ArchKinds.Load();
		var foundationMouths = ArchFoundationMouths.Load();
		var mouths = group.Rooms
			.SelectMany( room => ArchPlanStore.FiledOn( ArchKind.Approach, room, kinds ) )
			.Select( approach => foundationMouths.Open( ArchKind.Approach,
				new ArchFoundationMouthRequest( approach, kit, oversize + bite ) ) )
			.Where( mouth => mouth.Count >= 3 )
			.ToList();

		var open = mouths.Concat( wells.Select( well => (IReadOnlyList<Vector2>)well.Outline() ) ).ToList();

		Plinth( canvas, ArchFootprint.Subtract( ArchFootprint.Grow( footprint, oversize ), ArchFootprint.Union( open ) ),
			bottom, top, brush );

		var foundationOwner = building.Rooms
			.Where( room => room.Floor == FoundationLevel( building ) && room.HasFloor && ArchLayerGate.On( room ) )
			.OrderBy( room => room.Id )
			.FirstOrDefault();

		if ( ReferenceEquals( lead, foundationOwner ) ) {
			foreach ( var room in building.Rooms.Where( room => room.Floor < 0 ) ) {
				foreach ( var wall in room.Walls ) {
					foreach ( var opening in wall.Openings ) {
						ArchBasementWell.Build( canvas, ArchBasementWell.Resolve( room, wall, opening, building, kit ), brush );
					}
				}
			}
		}
	}

	// Every hole in the slab this footing carries: the stairwells stored on the building and the shafts a
	// cut derives, both asked with the SLAB's band so the footing loses exactly what the floor over it did.
	public static List<ArchFloorCutout> Wells( ArchPlan plan, ArchFloorGroup group, ArchBuilding building, ArchKit kit, float soffit, float top ) {
		var wells = ArchCut.Stored( building, group.Level )
			.Concat( ArchCut.Holes( plan, group.Level, kit, soffit, top, building.Id, ArchCutAffects.Foundations ) )
			.ToList();

		return wells;
	}

	// Lofted, not boxed: boxes over an L share a face; its caps are never seen.
	static void Plinth( ArchMesh canvas, List<List<Vector2>> region, float bottom, float top, ArchBrush brush ) {
		var solid = ArchFootprint.Outer( region ).ToList();

		if ( solid.Count != region.Count ) {
			Solid( canvas, region, null, bottom, top, brush );
			return;
		}

		foreach ( var loop in solid ) {
			ArchSlab.Face( canvas, loop.Select( point => new Vector3( point.x, point.y, bottom ) ).ToList(),
				Vector3.Up * (top - bottom), brush );
		}
	}
}