Editor/Data/ArchArchetypeRules.cs
using System;
using Sandbox;

namespace Sunless.Architecture;

// The only place a type touches geometry; a building keeps obeying its type.
public static class ArchArchetypeRules {
	// An unstated wall height is the kit's storey, never "leave what is standing" - a type that says
	// nothing has to be able to take a taller type's walls back down when it is adopted.
	public static float Standing( float authored, ArchKit kit ) => authored > 1f ? authored : kit.WallHeight;

	public static float Standing( ArchSectionRules rules, ArchKit kit ) => Standing( rules.WallHeight, kit );

	public static void Apply( ArchSection section, ArchSectionRules rules, ArchKit kit ) {
		if ( section?.Room is null || rules is null ) {
			return;
		}

		// Merged wings keep the height Extend already measured, else they'd stand open along their eave.
		if ( !section.Merged ) {
			section.Room.WallHeight = Standing( rules, kit );
		}

		section.Room.HasCeiling = rules.Ceiling ?? section.Room.HasCeiling;
		section.Room.HasFloor = rules.Floor ?? section.Room.HasFloor;
		section.Room.RaisedFoundation = rules.Foundation ?? section.Room.RaisedFoundation;
		section.Room.Collision = rules.Collision ?? section.Room.Collision;
		section.Room.FloorBoards = rules.FloorBoards;
		section.Room.FloorBoardYaw = rules.FloorBoardYaw;

		// One copier: a band added later reaches old elevations; no shared band list.
		if ( rules.Wall is { } treatment ) {
			foreach ( var wall in section.Room.Walls ) {
				ArchWallTreatments.Dress( treatment, wall );
			}
		}

		if ( rules.WallThickness > 0f )
		{
			foreach ( var wall in section.Room.Walls )
			{
				wall.Thickness = rules.WallThickness;
			}
		}

		Skin( section.Room.Palette, rules.Palette );

		if ( section.Roof is null ) {
			return;
		}

		if ( !section.Merged ) {
			section.Room.WallHeight = Plate( section.Room );
			section.Roof.BaseHeight = section.Room.BaseHeight + section.Room.WallHeight;
		}

		Roof( section.Roof, rules );
	}

	// The roof sits on the walls that actually stand - a treatment height moves it too.
	static float Plate( ArchRoom room ) {
		var authored = 0f;

		foreach ( var wall in room.Walls ) {
			authored = MathF.Max( authored, wall.Height );
		}

		return authored > 1f ? authored : room.WallHeight;
	}

	public static void Roof( ArchRoofPart roof, ArchSectionRules rules ) {
		if ( rules.RoofThickness > 0f )
		{
			roof.Thickness = rules.RoofThickness;
		}

		if ( rules.RoofPitch > 0.5f ) {
			roof.Pitch = rules.RoofPitch;
		}

		if ( rules.Overhang > 0.01f ) {
			roof.Overhang = rules.Overhang;
		}

		if ( rules.FrameSpacing > 4f ) {
			roof.FrameSpacing = rules.FrameSpacing;
		}

		if ( rules.ParapetHeight > 0.5f ) {
			roof.ParapetHeight = rules.ParapetHeight;
		}

		if ( rules.GuardHeight > 0.5f ) {
			roof.GuardHeight = rules.GuardHeight;
		}

		roof.Ceiling = rules.Ceiling ?? roof.Ceiling;
		roof.Gutters = rules.Gutters ?? roof.Gutters;
		roof.Fascia = rules.Fascia ?? roof.Fascia;
		roof.Soffit = rules.Soffit ?? roof.Soffit;
		roof.Frame = rules.Frame ?? roof.Frame;
		roof.Parapet = rules.Parapet ?? roof.Parapet;
		roof.Guardrail = rules.Guardrail ?? roof.Guardrail;

		Skin( roof.Palette, rules.Palette );
	}

	// Copied, not assigned - a shared dictionary would silently re-skin every part.
	public static void Skin( ArchPalette target, ArchPalette source ) {
		foreach ( var entry in source.Materials ) {
			target.Materials[entry.Key] = entry.Value;
		}

		foreach ( var entry in source.TexelScales ) {
			target.TexelScales[entry.Key] = entry.Value;
		}

		foreach ( var entry in source.TextureOffsets ?? new Dictionary<string, Vector2>() ) {
			target.TextureOffsets[entry.Key] = entry.Value;
		}
	}
}