Editor/Data/ArchStoreyEdit.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

public readonly record struct ArchStoreyEditResult( ArchRoom Room, int Level, string Error )
{
	public bool Changed => Room is not null && string.IsNullOrWhiteSpace( Error );
}

public static class ArchStoreyEdit
{
	public static ArchStoreyEditResult Add( ArchPlan plan, ArchBuilding building, ArchKit kit, int sourceLevel, int direction )
	{
		if ( plan is null || building is null || direction is not (-1 or 1) )
		{
			return new ArchStoreyEditResult( null, sourceLevel, "A storey can only be added immediately above or below a standing one." );
		}

		var source = building.Rooms
			.Where( room => room.Floor == sourceLevel && room.HasFootprint )
			.OrderByDescending( room => MathF.Abs( ArchFootprint.SignedArea( ArchFloorGen.Footprint( room ) ) ) )
			.FirstOrDefault();

		if ( source is null )
		{
			return new ArchStoreyEditResult( null, sourceLevel, "Nothing stands on this level to copy." );
		}

		var targetLevel = sourceLevel + direction;

		if ( building.Rooms.Any( room => room.Floor == targetLevel && room.HasContent ) )
		{
			return new ArchStoreyEditResult( null, targetLevel, $"{Name( targetLevel )} already exists." );
		}

		var room = new ArchRoom
		{
			Id = plan.AllocateId(),
			Name = Name( targetLevel ),
			Floor = targetLevel,
			BaseHeight = ArchBuild.FloorOf( building, kit, targetLevel ),
			WallHeight = source.WallHeight,
			HasFloor = true,
			HasCeiling = source.HasCeiling,
			CeilingDepth = source.CeilingDepth,
			RaisedFoundation = false,
			Footprint = new List<Vector2>( ArchFloorGen.Footprint( source ) )
		};

		foreach ( var sourceWall in source.Walls )
		{
			var wall = new ArchWall
			{
				Id = plan.AllocateId(),
				Start = sourceWall.Start,
				End = sourceWall.End,
				Exterior = sourceWall.Exterior,
				Thickness = sourceWall.Thickness
			};

			room.Walls.Add( wall );
		}

		building.Rooms.Add( room );

		if ( direction > 0 )
		{
			var lift = ArchBuild.FloorOf( building, kit, targetLevel ) - ArchBuild.FloorOf( building, kit, sourceLevel );

			foreach ( var roof in building.Roofs.Where( roof => roof.Level == sourceLevel ) )
			{
				roof.Level = targetLevel;
				roof.BaseHeight += lift;
			}
		}

		return new ArchStoreyEditResult( room, targetLevel, null );
	}

	public static ArchStoreyEditResult Remove( ArchPlan plan, ArchBuilding building, ArchKit kit, int level )
	{
		if ( plan is null || building is null )
		{
			return new ArchStoreyEditResult( null, level, "No building is active." );
		}

		var rooms = building.Rooms.Where( room => room.Floor == level ).ToList();
		var remainingLevels = building.Rooms
			.Where( room => room.Floor != level )
			.Select( room => room.Floor )
			.Distinct()
			.ToList();

		if ( rooms.Count == 0 )
		{
			return new ArchStoreyEditResult( null, level, $"{Name( level )} does not exist." );
		}

		if ( remainingLevels.Count == 0 )
		{
			return new ArchStoreyEditResult( null, level, "A building must keep at least one storey." );
		}

		if ( level == 0 )
		{
			return new ArchStoreyEditResult( null, level, "The ground floor anchors this building. Remove upper floors and basements from their outer ends." );
		}

		if ( level < 0 && remainingLevels.Any( candidate => candidate < level ) )
		{
			return new ArchStoreyEditResult( null, level, $"Remove {Name( remainingLevels.Min() )} first so no unsupported basement level is left below it." );
		}

		if ( level > 0 && remainingLevels.Any( candidate => candidate > level ) )
		{
			return new ArchStoreyEditResult( null, level, $"Remove {Name( remainingLevels.Max() )} first so no unsupported floor is left above it." );
		}

		var nextLevel = remainingLevels
			.OrderBy( candidate => Math.Abs( candidate - level ) )
			.ThenByDescending( candidate => candidate )
			.First();
		var shift = ArchBuild.FloorOf( building, kit, nextLevel ) - ArchBuild.FloorOf( building, kit, level );

		foreach ( var roof in building.Roofs.Where( roof => roof.Level == level ) )
		{
			roof.Level = nextLevel;
			roof.BaseHeight += shift;
		}

		foreach ( var room in rooms )
		{
			ArchAffectors.Close( plan, room );
			building.Rooms.Remove( room );
		}

		var nextRoom = building.Rooms.FirstOrDefault( room => room.Floor == nextLevel );

		return new ArchStoreyEditResult( nextRoom, nextLevel, null );
	}

	public static string Name( int level )
	{
		return level switch
		{
			< 0 => $"Basement {Math.Abs( level )}",
			0 => "Ground floor",
			_ => $"Floor {level}"
		};
	}
}