Editor/InteriorLayoutBuilder/RoomLayoutTool.Materials.cs
using System;
using System.Linq;

namespace ReusableRoomLayout;

public sealed partial class RoomLayoutTool
{
	public float WallHeight => document.Settings.WallHeight;
	public float DoorWidth => document.Settings.DoorWidth;
	public float DoorHeight => document.Settings.DoorHeight;
	public float DoorFrameThickness => document.Settings.DoorFrameThickness;
	public float WindowWidth => document.Settings.WindowWidth;
	public float WindowHeight => document.Settings.WindowHeight;
	public float WindowSillHeight => document.Settings.WindowSillHeight;
	public float WindowFrameThickness => document.Settings.WindowFrameThickness;
	public float? SelectedDoorWidth => SelectedDoor()?.Width;
	public float? SelectedDoorHeight => SelectedDoor()?.Height;
	public float? SelectedWindowWidth => SelectedWindow()?.Width;
	public float? SelectedWindowHeight => SelectedWindow()?.Height;
	public float? SelectedWindowSillHeight => SelectedWindow()?.SillHeight;
	public bool BaseboardsEnabled => document.Settings.BaseboardsEnabled;
	public bool ThresholdsEnabled => document.Settings.ThresholdsEnabled;
	public bool RoofEnabled => document.Settings.RoofEnabled;
	public float BaseboardHeight => document.Settings.BaseboardHeight;
	public float BaseboardDepth => document.Settings.BaseboardDepth;
	public float ThresholdDepth => document.Settings.ThresholdDepth;
	public float RoofThickness => document.Settings.RoofThickness;
	public float TextureWorldSize => document.Settings.TextureWorldSize;
	public bool HasSelectedLayout =>
		document.FindDoor( selectedDoorId ) is not null ||
		document.FindWindow( selectedWindowId ) is not null ||
		SelectedCorridor() is not null ||
		SelectedFloorCutout() is not null ||
		document.FindRoom( selectedRoomId ) is not null;
	public bool HasSelectedRoom => selectedDoorId == 0 && selectedWindowId == 0 && document.FindRoom( selectedRoomId ) is not null;
	public bool HasSelectedDoor => SelectedDoor() is not null;
	public bool HasSelectedWindow => SelectedWindow() is not null;
	public bool HasSelectedCorridor => SelectedCorridor() is not null;
	public bool HasRooms => document.Rooms.Count > 0;
	public string SelectedRoomName => document.FindRoom( selectedRoomId ) is { } room
		? $"{FloorLabel( document.FloorFor( room ) )}: {(string.IsNullOrWhiteSpace( room.Name ) ? $"Room {room.Id}" : room.Name)}"
		: "No room selected";
	public string SelectedCorridorName => SelectedCorridor() is { } corridor ? $"{FloorLabel( document.FloorFor( corridor ) )}: Corridor {corridor.Id}" : "No corridor selected";
	public string SelectedLayoutName => document.FindDoor( selectedDoorId ) is { } door
		? $"{FloorLabel( document.FloorFor( door ) )}: Door {door.Id}"
		: document.FindWindow( selectedWindowId ) is { } window
			? $"{FloorLabel( document.FloorFor( window ) )}: Window {window.Id}"
			: SelectedCorridor() is { } corridor
				? $"{FloorLabel( document.FloorFor( corridor ) )}: Corridor {corridor.Id}"
				: SelectedFloorCutout() is { } cutout
					? $"{FloorLabel( document.FloorFor( cutout ) )}: {(string.IsNullOrWhiteSpace( cutout.Name ) ? $"Floor Cutout {cutout.Id}" : cutout.Name)}"
					: document.FindRoom( selectedRoomId ) is { } room
						? $"{FloorLabel( document.FloorFor( room ) )}: {(string.IsNullOrWhiteSpace( room.Name ) ? $"Room {room.Id}" : room.Name)}"
						: "None";

	private RoomLayoutWindow SelectedWindow()
	{
		return document.FindWindow( selectedWindowId );
	}

	private RoomLayoutDoor SelectedDoor()
	{
		return document.FindDoor( selectedDoorId );
	}

	public bool CanSetSelectedMaterialSlot( RoomLayoutMaterialSlot slot )
	{
		return slot switch
		{
			RoomLayoutMaterialSlot.DoorFrame => SelectedDoor() is not null,
			RoomLayoutMaterialSlot.WindowFrame => HasSelectedWindow,
			RoomLayoutMaterialSlot.Floor or
				RoomLayoutMaterialSlot.Wall or
				RoomLayoutMaterialSlot.OuterWall or
				RoomLayoutMaterialSlot.InnerWall or
				RoomLayoutMaterialSlot.WallCap or
				RoomLayoutMaterialSlot.Baseboard or
				RoomLayoutMaterialSlot.Threshold or
				RoomLayoutMaterialSlot.Roof => HasSelectedRoom || HasSelectedCorridor,
			_ => false
		};
	}

	public string GetMaterialPath( RoomLayoutMaterialSlot slot )
	{
		var settings = document.Settings;
		return slot switch
		{
			RoomLayoutMaterialSlot.Floor => settings.FloorMaterialPath,
			RoomLayoutMaterialSlot.Wall => settings.WallMaterialPath,
			RoomLayoutMaterialSlot.OuterWall => FirstMaterialPath( settings.OuterWallMaterialPath, settings.WallMaterialPath ),
			RoomLayoutMaterialSlot.InnerWall => FirstMaterialPath( settings.InnerWallMaterialPath, settings.WallMaterialPath ),
			RoomLayoutMaterialSlot.WallCap => settings.WallCapMaterialPath,
			RoomLayoutMaterialSlot.Baseboard => settings.BaseboardMaterialPath,
			RoomLayoutMaterialSlot.DoorFrame => settings.DoorFrameMaterialPath,
			RoomLayoutMaterialSlot.WindowFrame => settings.WindowFrameMaterialPath,
			RoomLayoutMaterialSlot.CorridorFloor => settings.CorridorFloorMaterialPath,
			RoomLayoutMaterialSlot.Threshold => settings.ThresholdMaterialPath,
			RoomLayoutMaterialSlot.Roof => settings.RoofMaterialPath,
			_ => ""
		};
	}

	public string GetSelectedMaterialPath( RoomLayoutMaterialSlot slot )
	{
		if ( SelectedDoor() is { } door )
		{
			return slot == RoomLayoutMaterialSlot.DoorFrame
				? door.DoorFrameMaterialPath
				: "";
		}

		if ( SelectedWindow() is { } window )
		{
			return slot == RoomLayoutMaterialSlot.WindowFrame
				? window.WindowFrameMaterialPath
				: "";
		}

		if ( HasSelectedRoom && document.FindRoom( selectedRoomId ) is { } room )
		{
			return slot switch
			{
				RoomLayoutMaterialSlot.Floor => room.FloorMaterialPath,
				RoomLayoutMaterialSlot.Wall => room.WallMaterialPath,
				RoomLayoutMaterialSlot.OuterWall => FirstMaterialPath( room.OuterWallMaterialPath, room.WallMaterialPath ),
				RoomLayoutMaterialSlot.InnerWall => room.InnerWallMaterialPath,
				RoomLayoutMaterialSlot.WallCap => room.WallCapMaterialPath,
				RoomLayoutMaterialSlot.Baseboard => room.BaseboardMaterialPath,
				RoomLayoutMaterialSlot.Threshold => room.ThresholdMaterialPath,
				RoomLayoutMaterialSlot.Roof => room.RoofMaterialPath,
				_ => ""
			};
		}

		if ( SelectedCorridor() is { } corridor )
		{
			return slot switch
			{
				RoomLayoutMaterialSlot.Floor => corridor.FloorMaterialPath,
				RoomLayoutMaterialSlot.Wall => corridor.WallMaterialPath,
				RoomLayoutMaterialSlot.OuterWall => FirstMaterialPath( corridor.OuterWallMaterialPath, corridor.WallMaterialPath ),
				RoomLayoutMaterialSlot.InnerWall => corridor.InnerWallMaterialPath,
				RoomLayoutMaterialSlot.WallCap => corridor.WallCapMaterialPath,
				RoomLayoutMaterialSlot.Baseboard => corridor.BaseboardMaterialPath,
				RoomLayoutMaterialSlot.Threshold => corridor.ThresholdMaterialPath,
				RoomLayoutMaterialSlot.Roof => corridor.RoofMaterialPath,
				_ => ""
			};
		}

		return "";
	}

	public float GetMaterialScale( RoomLayoutMaterialSlot slot )
	{
		var settings = document.Settings;
		return slot switch
		{
			RoomLayoutMaterialSlot.Floor => MaterialScaleOrFallback( settings.FloorMaterialScale, settings.TextureWorldSize ),
			RoomLayoutMaterialSlot.Wall => MaterialScaleOrFallback( settings.WallMaterialScale, settings.TextureWorldSize ),
			RoomLayoutMaterialSlot.OuterWall => MaterialScaleOrFallback( settings.OuterWallMaterialScale, MaterialScaleOrFallback( settings.WallMaterialScale, settings.TextureWorldSize ) ),
			RoomLayoutMaterialSlot.InnerWall => MaterialScaleOrFallback( settings.InnerWallMaterialScale, MaterialScaleOrFallback( settings.WallMaterialScale, settings.TextureWorldSize ) ),
			RoomLayoutMaterialSlot.WallCap => MaterialScaleOrFallback( settings.WallCapMaterialScale, settings.TextureWorldSize ),
			RoomLayoutMaterialSlot.Baseboard => MaterialScaleOrFallback( settings.BaseboardMaterialScale, settings.TextureWorldSize ),
			RoomLayoutMaterialSlot.DoorFrame => MaterialScaleOrFallback( settings.DoorFrameMaterialScale, settings.TextureWorldSize ),
			RoomLayoutMaterialSlot.WindowFrame => MaterialScaleOrFallback( settings.WindowFrameMaterialScale, settings.TextureWorldSize ),
			RoomLayoutMaterialSlot.CorridorFloor => MaterialScaleOrFallback( settings.CorridorFloorMaterialScale, settings.TextureWorldSize ),
			RoomLayoutMaterialSlot.Threshold => MaterialScaleOrFallback( settings.ThresholdMaterialScale, settings.TextureWorldSize ),
			RoomLayoutMaterialSlot.Roof => MaterialScaleOrFallback( settings.RoofMaterialScale, settings.TextureWorldSize ),
			_ => MaterialScaleOrFallback( 0.0f, settings.TextureWorldSize )
		};
	}

	public float GetSelectedMaterialScale( RoomLayoutMaterialSlot slot )
	{
		if ( SelectedDoor() is { } door )
		{
			return slot == RoomLayoutMaterialSlot.DoorFrame
				? door.DoorFrameMaterialScale
				: 0.0f;
		}

		if ( SelectedWindow() is { } window )
		{
			return slot == RoomLayoutMaterialSlot.WindowFrame
				? window.WindowFrameMaterialScale
				: 0.0f;
		}

		if ( HasSelectedRoom && document.FindRoom( selectedRoomId ) is { } room )
		{
			return slot switch
			{
				RoomLayoutMaterialSlot.Floor => room.FloorMaterialScale,
				RoomLayoutMaterialSlot.Wall => room.WallMaterialScale,
				RoomLayoutMaterialSlot.OuterWall => FirstMaterialScale( room.OuterWallMaterialScale, room.WallMaterialScale ),
				RoomLayoutMaterialSlot.InnerWall => room.InnerWallMaterialScale,
				RoomLayoutMaterialSlot.WallCap => room.WallCapMaterialScale,
				RoomLayoutMaterialSlot.Baseboard => room.BaseboardMaterialScale,
				RoomLayoutMaterialSlot.Threshold => room.ThresholdMaterialScale,
				RoomLayoutMaterialSlot.Roof => room.RoofMaterialScale,
				_ => 0.0f
			};
		}

		if ( SelectedCorridor() is { } corridor )
		{
			return slot switch
			{
				RoomLayoutMaterialSlot.Floor => corridor.FloorMaterialScale,
				RoomLayoutMaterialSlot.Wall => corridor.WallMaterialScale,
				RoomLayoutMaterialSlot.OuterWall => FirstMaterialScale( corridor.OuterWallMaterialScale, corridor.WallMaterialScale ),
				RoomLayoutMaterialSlot.InnerWall => corridor.InnerWallMaterialScale,
				RoomLayoutMaterialSlot.WallCap => corridor.WallCapMaterialScale,
				RoomLayoutMaterialSlot.Baseboard => corridor.BaseboardMaterialScale,
				RoomLayoutMaterialSlot.Threshold => corridor.ThresholdMaterialScale,
				RoomLayoutMaterialSlot.Roof => corridor.RoofMaterialScale,
				_ => 0.0f
			};
		}

		return 0.0f;
	}

	public float GetEffectiveSelectedMaterialScale( RoomLayoutMaterialSlot slot )
	{
		var selectedScale = GetSelectedMaterialScale( slot );
		if ( selectedScale > 0.0f )
		{
			return selectedScale;
		}

		if ( SelectedCorridor() is not null && slot == RoomLayoutMaterialSlot.Floor )
		{
			return GetMaterialScale( RoomLayoutMaterialSlot.CorridorFloor );
		}

		return GetMaterialScale( slot );
	}

	public void SetMaterialPath( RoomLayoutMaterialSlot slot, string path )
	{
		EnsureLayoutLoadedForControls();

		path = NormalizeMaterialPath( path );
		if ( string.Equals( GetMaterialPath( slot ), path, StringComparison.OrdinalIgnoreCase ) )
		{
			return;
		}

		PushLayoutUndo();
		var settings = document.Settings;
		switch ( slot )
		{
			case RoomLayoutMaterialSlot.Floor:
				settings.FloorMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.Wall:
				settings.WallMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.OuterWall:
				settings.OuterWallMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.InnerWall:
				settings.InnerWallMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.WallCap:
				settings.WallCapMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.Baseboard:
				settings.BaseboardMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.DoorFrame:
				settings.DoorFrameMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.WindowFrame:
				settings.WindowFrameMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.CorridorFloor:
				settings.CorridorFloorMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.Threshold:
				settings.ThresholdMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.Roof:
				settings.RoofMaterialPath = path;
				break;
		}

		CommitLayoutChange();
	}

	public void SetMaterialScale( RoomLayoutMaterialSlot slot, float scale )
	{
		EnsureLayoutLoadedForControls();

		scale = NormalizeMaterialScale( scale, allowInherit: true );
		if ( Math.Abs( GetMaterialScaleOverride( slot ) - scale ) < 0.001f )
		{
			return;
		}

		PushLayoutUndo();
		var settings = document.Settings;
		switch ( slot )
		{
			case RoomLayoutMaterialSlot.Floor:
				settings.FloorMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.Wall:
				settings.WallMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.OuterWall:
				settings.OuterWallMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.InnerWall:
				settings.InnerWallMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.WallCap:
				settings.WallCapMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.Baseboard:
				settings.BaseboardMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.DoorFrame:
				settings.DoorFrameMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.WindowFrame:
				settings.WindowFrameMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.CorridorFloor:
				settings.CorridorFloorMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.Threshold:
				settings.ThresholdMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.Roof:
				settings.RoofMaterialScale = scale;
				break;
		}

		CommitLayoutChange();
	}

	public void SetSelectedMaterialPath( RoomLayoutMaterialSlot slot, string path )
	{
		EnsureLayoutLoadedForControls();

		if ( !CanSetSelectedMaterialSlot( slot ) )
		{
			return;
		}

		path = NormalizeMaterialPath( path );
		if ( string.Equals( GetSelectedMaterialPath( slot ), path, StringComparison.OrdinalIgnoreCase ) )
		{
			return;
		}

		if ( SelectedDoor() is { } door )
		{
			PushLayoutUndo();
			door.DoorFrameMaterialPath = path;
			CommitLayoutChange();
			return;
		}

		if ( SelectedWindow() is { } window )
		{
			PushLayoutUndo();
			window.WindowFrameMaterialPath = path;
			CommitLayoutChange();
			return;
		}

		if ( HasSelectedRoom && document.FindRoom( selectedRoomId ) is { } room )
		{
			PushLayoutUndo();
			switch ( slot )
			{
				case RoomLayoutMaterialSlot.Floor:
					room.FloorMaterialPath = path;
					break;
				case RoomLayoutMaterialSlot.Wall:
					room.WallMaterialPath = path;
					break;
				case RoomLayoutMaterialSlot.OuterWall:
					room.OuterWallMaterialPath = path;
					room.WallMaterialPath = "";
					break;
				case RoomLayoutMaterialSlot.InnerWall:
					room.InnerWallMaterialPath = path;
					break;
				case RoomLayoutMaterialSlot.WallCap:
					room.WallCapMaterialPath = path;
					break;
				case RoomLayoutMaterialSlot.Baseboard:
					room.BaseboardMaterialPath = path;
					break;
				case RoomLayoutMaterialSlot.Threshold:
					room.ThresholdMaterialPath = path;
					break;
				case RoomLayoutMaterialSlot.Roof:
					room.RoofMaterialPath = path;
					break;
			}

			CommitLayoutChange();
			return;
		}

		if ( SelectedCorridor() is not { } corridor )
		{
			return;
		}

		PushLayoutUndo();
		switch ( slot )
		{
			case RoomLayoutMaterialSlot.Floor:
				corridor.FloorMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.Wall:
				corridor.WallMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.OuterWall:
				corridor.OuterWallMaterialPath = path;
				corridor.WallMaterialPath = "";
				break;
			case RoomLayoutMaterialSlot.InnerWall:
				corridor.InnerWallMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.WallCap:
				corridor.WallCapMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.Baseboard:
				corridor.BaseboardMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.Threshold:
				corridor.ThresholdMaterialPath = path;
				break;
			case RoomLayoutMaterialSlot.Roof:
				corridor.RoofMaterialPath = path;
				break;
		}

		CommitLayoutChange();
	}

	public void SetSelectedMaterialScale( RoomLayoutMaterialSlot slot, float scale )
	{
		EnsureLayoutLoadedForControls();

		if ( !CanSetSelectedMaterialSlot( slot ) )
		{
			return;
		}

		scale = NormalizeMaterialScale( scale, allowInherit: true );
		if ( Math.Abs( GetSelectedMaterialScale( slot ) - scale ) < 0.001f )
		{
			return;
		}

		if ( SelectedDoor() is { } door )
		{
			PushLayoutUndo();
			door.DoorFrameMaterialScale = scale;
			CommitLayoutChange();
			return;
		}

		if ( SelectedWindow() is { } window )
		{
			PushLayoutUndo();
			window.WindowFrameMaterialScale = scale;
			CommitLayoutChange();
			return;
		}

		if ( HasSelectedRoom && document.FindRoom( selectedRoomId ) is { } room )
		{
			PushLayoutUndo();
			switch ( slot )
			{
				case RoomLayoutMaterialSlot.Floor:
					room.FloorMaterialScale = scale;
					break;
				case RoomLayoutMaterialSlot.Wall:
					room.WallMaterialScale = scale;
					break;
				case RoomLayoutMaterialSlot.OuterWall:
					room.OuterWallMaterialScale = scale;
					room.WallMaterialScale = 0.0f;
					break;
				case RoomLayoutMaterialSlot.InnerWall:
					room.InnerWallMaterialScale = scale;
					break;
				case RoomLayoutMaterialSlot.WallCap:
					room.WallCapMaterialScale = scale;
					break;
				case RoomLayoutMaterialSlot.Baseboard:
					room.BaseboardMaterialScale = scale;
					break;
				case RoomLayoutMaterialSlot.Threshold:
					room.ThresholdMaterialScale = scale;
					break;
				case RoomLayoutMaterialSlot.Roof:
					room.RoofMaterialScale = scale;
					break;
			}

			CommitLayoutChange();
			return;
		}

		if ( SelectedCorridor() is not { } corridor )
		{
			return;
		}

		PushLayoutUndo();
		switch ( slot )
		{
			case RoomLayoutMaterialSlot.Floor:
				corridor.FloorMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.Wall:
				corridor.WallMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.OuterWall:
				corridor.OuterWallMaterialScale = scale;
				corridor.WallMaterialScale = 0.0f;
				break;
			case RoomLayoutMaterialSlot.InnerWall:
				corridor.InnerWallMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.WallCap:
				corridor.WallCapMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.Baseboard:
				corridor.BaseboardMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.Threshold:
				corridor.ThresholdMaterialScale = scale;
				break;
			case RoomLayoutMaterialSlot.Roof:
				corridor.RoofMaterialScale = scale;
				break;
		}

		CommitLayoutChange();
	}

	public void SetTextureWorldSize( float size )
	{
		EnsureLayoutLoadedForControls();

		size = Math.Clamp( size, 1.0f, 8192.0f );
		if ( Math.Abs( document.Settings.TextureWorldSize - size ) < 0.001f )
		{
			return;
		}

		PushLayoutUndo();
		document.Settings.TextureWorldSize = size;
		CommitLayoutChange();
	}

	public void SetWallHeight( float height )
	{
		EnsureLayoutLoadedForControls();

		height = Math.Clamp( height, 8.0f, 4096.0f );
		if ( Math.Abs( document.Settings.WallHeight - height ) < 0.001f )
		{
			return;
		}

		PushLayoutUndo();
		document.Settings.WallHeight = height;
		CommitLayoutChange();
	}

	public void SetWindowWidth( float width )
	{
		SetFloatSetting( width, 1.0f, 4096.0f, () => document.Settings.WindowWidth, value => document.Settings.WindowWidth = value );
	}

	private float WindowWallLength( RoomLayoutWindow window )
	{
		if ( window.CorridorId != 0 )
		{
			var corridor = document.Corridors.FirstOrDefault( candidate => candidate.Id == window.CorridorId );
			if ( corridor is not null &&
				TryGetCorridorPath( corridor, out var points ) &&
				window.CorridorSegmentIndex >= 0 &&
				window.CorridorSegmentIndex < points.Count - 1 )
			{
				var a = points[window.CorridorSegmentIndex];
				var b = points[window.CorridorSegmentIndex + 1];
				return MathF.Abs( b.x - a.x ) >= MathF.Abs( b.y - a.y )
					? MathF.Abs( b.x - a.x )
					: MathF.Abs( b.y - a.y );
			}

			return 4096.0f;
		}

		var room = document.FindRoom( window.RoomId );
		if ( room is null )
		{
			return 4096.0f;
		}

		return window.Side is RoomLayoutWallSide.North or RoomLayoutWallSide.South
			? room.Bounds.Width
			: room.Bounds.Height;
	}

	public void SetWindowHeight( float height )
	{
		SetFloatSetting( height, 1.0f, 4096.0f, () => document.Settings.WindowHeight, value => document.Settings.WindowHeight = value );
	}

	public void SetWindowSillHeight( float height )
	{
		SetFloatSetting( height, 0.0f, 4096.0f, () => document.Settings.WindowSillHeight, value => document.Settings.WindowSillHeight = value );
	}

	public void SetWindowFrameThickness( float thickness )
	{
		SetFloatSetting( thickness, 0.1f, 512.0f, () => document.Settings.WindowFrameThickness, value => document.Settings.WindowFrameThickness = value );
	}

	public void SetDoorWidth( float width )
	{
		SetFloatSetting( width, 1.0f, 4096.0f, () => document.Settings.DoorWidth, value => document.Settings.DoorWidth = value );
	}

	public void SetDoorHeight( float height )
	{
		SetFloatSetting( height, 1.0f, 4096.0f, () => document.Settings.DoorHeight, value => document.Settings.DoorHeight = value );
	}

	public void SetDoorFrameThickness( float thickness )
	{
		SetFloatSetting( thickness, 0.1f, 512.0f, () => document.Settings.DoorFrameThickness, value => document.Settings.DoorFrameThickness = value );
	}

	public void SetBaseboardsEnabled( bool enabled )
	{
		EnsureLayoutLoadedForControls();

		if ( document.Settings.BaseboardsEnabled == enabled )
		{
			return;
		}

		PushLayoutUndo();
		document.Settings.BaseboardsEnabled = enabled;
		CommitLayoutChange();
	}

	public void SetThresholdsEnabled( bool enabled )
	{
		EnsureLayoutLoadedForControls();

		if ( document.Settings.ThresholdsEnabled == enabled )
		{
			return;
		}

		PushLayoutUndo();
		document.Settings.ThresholdsEnabled = enabled;
		CommitLayoutChange();
	}

	public void SetRoofEnabled( bool enabled )
	{
		EnsureLayoutLoadedForControls();

		if ( document.Settings.RoofEnabled == enabled )
		{
			return;
		}

		PushLayoutUndo();
		document.Settings.RoofEnabled = enabled;
		CommitLayoutChange();
	}

	public void SetBaseboardHeight( float height )
	{
		SetFloatSetting( height, 0.5f, 512.0f, () => document.Settings.BaseboardHeight, value => document.Settings.BaseboardHeight = value );
	}

	public void SetBaseboardDepth( float depth )
	{
		SetFloatSetting( depth, 0.5f, 512.0f, () => document.Settings.BaseboardDepth, value => document.Settings.BaseboardDepth = value );
	}

	public void SetThresholdDepth( float depth )
	{
		SetFloatSetting( depth, 0.5f, 512.0f, () => document.Settings.ThresholdDepth, value => document.Settings.ThresholdDepth = value );
	}

	public void SetRoofThickness( float thickness )
	{
		SetFloatSetting( thickness, 0.5f, 512.0f, () => document.Settings.RoofThickness, value => document.Settings.RoofThickness = value );
	}

	private void SetFloatSetting( float value, float min, float max, Func<float> getter, Action<float> setter )
	{
		EnsureLayoutLoadedForControls();

		value = Math.Clamp( value, min, max );
		if ( Math.Abs( getter() - value ) < 0.001f )
		{
			return;
		}

		PushLayoutUndo();
		setter( value );
		CommitLayoutChange();
	}

	private static string NormalizeMaterialPath( string path )
	{
		if ( string.IsNullOrWhiteSpace( path ) )
		{
			return "";
		}

		path = path.Trim().Replace( '\\', '/' ).TrimStart( '/' );
		if ( path.StartsWith( "assets/", StringComparison.OrdinalIgnoreCase ) )
		{
			path = path[7..];
		}

		return path.EndsWith( ".vmat_c", StringComparison.OrdinalIgnoreCase )
			? path[..^2]
			: path;
	}

	private static string FirstMaterialPath( string preferred, string fallback )
	{
		return string.IsNullOrWhiteSpace( preferred )
			? fallback ?? ""
			: preferred;
	}

	private float GetMaterialScaleOverride( RoomLayoutMaterialSlot slot )
	{
		var settings = document.Settings;
		return slot switch
		{
			RoomLayoutMaterialSlot.Floor => settings.FloorMaterialScale,
			RoomLayoutMaterialSlot.Wall => settings.WallMaterialScale,
			RoomLayoutMaterialSlot.OuterWall => settings.OuterWallMaterialScale,
			RoomLayoutMaterialSlot.InnerWall => settings.InnerWallMaterialScale,
			RoomLayoutMaterialSlot.WallCap => settings.WallCapMaterialScale,
			RoomLayoutMaterialSlot.Baseboard => settings.BaseboardMaterialScale,
			RoomLayoutMaterialSlot.DoorFrame => settings.DoorFrameMaterialScale,
			RoomLayoutMaterialSlot.WindowFrame => settings.WindowFrameMaterialScale,
			RoomLayoutMaterialSlot.CorridorFloor => settings.CorridorFloorMaterialScale,
			RoomLayoutMaterialSlot.Threshold => settings.ThresholdMaterialScale,
			RoomLayoutMaterialSlot.Roof => settings.RoofMaterialScale,
			_ => 0.0f
		};
	}

	private static float FirstMaterialScale( float preferred, float fallback )
	{
		return preferred > 0.0f ? preferred : MathF.Max( 0.0f, fallback );
	}

	private static float NormalizeMaterialScale( float scale, bool allowInherit = false )
	{
		if ( allowInherit && scale <= 0.0f )
		{
			return 0.0f;
		}

		return Math.Clamp( scale, 1.0f, 8192.0f );
	}

	private static float MaterialScaleOrFallback( float scale, float fallback )
	{
		return Math.Clamp( scale > 0.0f ? scale : fallback, 1.0f, 8192.0f );
	}
}

public enum RoomLayoutMaterialSlot
{
	Floor,
	Wall,
	OuterWall,
	InnerWall,
	WallCap,
	Baseboard,
	DoorFrame,
	WindowFrame,
	CorridorFloor,
	Threshold,
	Roof
}