Editor/Designers/ArchBuildingDesigner.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;

namespace Sunless.Architecture;

// Authors a building TYPE only - nothing placed on it afterwards is staged here.
public sealed class ArchBuildingDesigner : ArchDesignerDialog {
	readonly ArchTool tool;
	readonly Action<ArchArchetype> applied;
	readonly ArchArchetype draft;
	Vector2 plot = new( 480f, 312f );
	RoofStyle roof = RoofStyle.Flat;
	bool withRoof = true;

	public ArchBuildingDesigner( Widget parent, ArchTool tool, ArchArchetype initial, Action<ArchArchetype> applied )
		: base( parent, "Building Designer", "domain" ) {
		this.tool = tool;
		this.applied = applied;
		draft = Copy( initial );

		if ( draft.Shell.Roof is { } style ) {
			roof = style;
		}

		Assemble(
			tool.Kit,
			"Live generated building",
			"Drag the preview to orbit. A whole unit through the same shell, wall and roof generators the plan builds with - what you see is what a drag will stand.",
			$"Building types: Assets/{ArchStorage.ArchetypeDirectory}" );
	}

	protected override string SaveLabel => "Save Type";

	protected override void Populate() {
		var form = Form.Layout;

		form.Add( Heading( "Identity" ) );
		form.Add( Text( "Name", draft.Name, value => draft.Name = value ) );
		form.Add( Text( "Title", draft.Title, value => draft.Title = value ) );
		form.Add( Text( "Description", draft.Description, value => draft.Description = value ) );

		form.Add( Heading( "Preview plot" ) );
		form.Add( Number( "Along x", plot.x, value => plot = plot.WithX( MathF.Max( 96f, value ) ) ) );
		form.Add( Number( "Along y", plot.y, value => plot = plot.WithY( MathF.Max( 96f, value ) ) ) );

		var options = new ToolSidebarWidget();

		Shell( options );
		Walls( options );
		Roof( options );

		form.Add( options );
	}

	void Shell( ToolSidebarWidget panel ) {
		var group = panel.AddGroup( "Shell" );

		group.Add( ArchPartUi.Number( "Wall height", draft.Shell.WallHeight, tool.Kit.WallHeight,
			value => draft.Shell.WallHeight = value, Stage.Restage ) );

		using var grid = ArchIconGrid.In( group );

		grid.Toggle( "Floor slab", "opt_floor_slab", "layers", draft.Shell.Floor ?? true,
			value => { draft.Shell.Floor = value; Stage.Restage(); } );

		grid.Toggle( "Stands on a raised foundation", "opt_raised", "foundation", draft.Shell.Foundation ?? true,
			value => { draft.Shell.Foundation = value; Stage.Restage(); } );

		grid.Toggle( "Ceiling under the roof", "opt_ceiling", "square", draft.Shell.Ceiling ?? false,
			value => { draft.Shell.Ceiling = value; Stage.Restage(); } );
	}

	void Walls( ToolSidebarWidget panel ) {
		draft.Shell.Wall ??= new ArchWallPreset();

		using ( var grid = ArchIconGrid.In( panel.AddGroup( "Treatment" ) ) ) {
			foreach ( var preset in tool.Kit.Walls ) {
				var captured = preset;

				grid.Pick( captured.Name, ArchIcons.WallSlug( captured.Name ), "villa",
					string.Equals( draft.Shell.Wall.Name, captured.Name, StringComparison.OrdinalIgnoreCase ),
					() => {
						draft.Shell.Wall = ArchWallPreset.Copy( captured );
						Repopulate();
					} );
			}
		}

		ArchWallForms.Wall( panel, tool.Kit, draft.Shell.Wall, Repopulate, Stage.Restage );
	}

	void Roof( ToolSidebarWidget panel ) {
		using ( var grid = ArchIconGrid.In( panel.AddGroup( "Roof" ) ) ) {
			grid.Toggle( "Roof over it", "opt_roof", "roofing", withRoof, value => { withRoof = value; Repopulate(); } );
		}

		if ( !withRoof ) {
			return;
		}

		using ( var grid = ArchIconGrid.In( panel.AddGroup( "Roof style" ) ) ) {
			foreach ( var value in Enum.GetValues<RoofStyle>() ) {
				var captured = value;

				grid.Pick( captured.ToString(), ArchIcons.RoofStyleSlug( captured ), ArchIcons.RoofStyleGlyph( captured ),
					roof == captured, () => { roof = captured; draft.Shell.Roof = captured; Repopulate(); } );
			}
		}

		var group = panel.AddGroup( "Deck" );

		group.Add( ArchPartUi.Number( "Pitch", draft.Shell.RoofPitch, tool.Kit.RoofPitch,
			value => draft.Shell.RoofPitch = value, Stage.Restage ) );

		group.Add( ArchPartUi.Number( "Overhang", draft.Shell.Overhang, 16f, value => draft.Shell.Overhang = value, Stage.Restage ) );

		using ( var grid = ArchIconGrid.In( group ) ) {
			grid.Toggle( "Parapet standing past the deck with a coping on it", "opt_parapet", "crop_din", draft.Shell.Parapet ?? false,
				value => { draft.Shell.Parapet = value; Repopulate(); } );

			grid.Toggle( "Gutters and downpipes", "opt_gutters", "water_damage", draft.Shell.Gutters ?? true,
				value => { draft.Shell.Gutters = value; Stage.Restage(); } );

			grid.Toggle( "Fascia along the eaves", "opt_fascia", "border_bottom", draft.Shell.Fascia ?? true,
				value => { draft.Shell.Fascia = value; Stage.Restage(); } );

			grid.Toggle( "Lined soffit under the overhang", "opt_soffit", "flip_to_back", draft.Shell.Soffit ?? true,
				value => { draft.Shell.Soffit = value; Stage.Restage(); } );

			grid.Toggle( "Exposed rafters and purlins under the deck", "opt_roof_frame", "reorder", draft.Shell.Frame ?? false,
				value => { draft.Shell.Frame = value; Stage.Restage(); } );
		}

		if ( draft.Shell.Parapet == true ) {
			group.Add( ArchPartUi.Number( "Parapet height", draft.Shell.ParapetHeight, 18f,
				value => draft.Shell.ParapetHeight = value, Stage.Restage ) );
		}
	}

	protected override ArchStaged Staged() => ArchArchetypeStage.Of( Snapshot(), tool.Kit, plot, withRoof ? roof : null );

	protected override string Diagnose() {
		var recipe = Snapshot();
		var wall = recipe.Shell.Wall;
		var staged = Staged().Plan;
		var room = staged?.Buildings.FirstOrDefault()?.Rooms.FirstOrDefault();

		return string.Join( "\n",
			$"type: {recipe.Name} ({recipe.Title})",
			$"plot: {plot.x:0.##} x {plot.y:0.##}, wall height {ArchArchetypeRules.Standing( recipe.Shell, tool.Kit ):0.##}",
			$"shell: {room?.Walls.Count ?? 0} walls, floor {recipe.Shell.Floor}, foundation {recipe.Shell.Foundation}, ceiling {recipe.Shell.Ceiling}",
			$"roof: {(withRoof ? roof.ToString() : "none")} pitch {recipe.Shell.RoofPitch:0.##} overhang {recipe.Shell.Overhang:0.##}, "
				+ $"parapet {recipe.Shell.Parapet} at {recipe.Shell.ParapetHeight:0.##}, frame {recipe.Shell.Frame}",
			$"walls: cladding {wall?.Cladding}, {wall?.Bands.Count ?? 0} bands "
				+ $"[{string.Join( " ", ArchWallTreatments.Ordered( wall?.Bands, room?.WallHeight ?? tool.Kit.WallHeight )
					.Select( band => $"{(band.AtHead ? "head" : band.Height.ToString( "0.#" ))}:{band.Field}" ) )}]",
			$"pilasters: " + (wall?.Pilasters.Stands == true
				? $"{wall.Pilasters.Bay:0.##} bay, {ArchWallSection.PilasterCentres( wall.Pilasters, 0f, plot.x ).Count()} strips over {plot.x:0.##}"
				: "none") );
	}

	protected override void Apply() {
		applied?.Invoke( Snapshot() );
		Close();
	}

	protected override void SaveTemplate() {
		var saved = Snapshot();

		if ( string.IsNullOrWhiteSpace( saved.Name ) ) {
			Log.Warning( "Architecture: the building type needs a name before it can be saved." );
			return;
		}

		if ( !ArchStorage.SaveArchetype( saved ) ) {
			Log.Warning( $"Architecture: could not write the building type '{saved.Name}'." );
			return;
		}

		tool.ReloadArchetypes();
		applied?.Invoke( saved );
		Close();
	}

	ArchArchetype Snapshot() {
		var result = Copy( draft );

		result.Name = string.IsNullOrWhiteSpace( draft.Name ) ? "building" : draft.Name.Trim();
		result.Title = string.IsNullOrWhiteSpace( draft.Title ) ? result.Name : draft.Title.Trim();
		result.Shell.Roof = withRoof ? roof : null;

		// The wing inherits the shell's treatment so an extension matches; its roof stays as authored.
		result.Wing.Wall = result.Shell.Wall is null ? null : ArchWallPreset.Copy( result.Shell.Wall );
		result.Wing.WallHeight = result.Shell.WallHeight;
		result.Wing.Parapet = result.Shell.Parapet;
		result.Wing.ParapetHeight = result.Shell.ParapetHeight;
		result.Wing.Fascia = result.Shell.Fascia;
		result.Wing.Soffit = result.Shell.Soffit;
		result.Wing.Frame = result.Shell.Frame;
		result.Wing.Gutters = result.Shell.Gutters;

		return result;
	}

	// Deep copy: a cancelled designer must leave the on-disk type exactly as it was.
	public static ArchArchetype Copy( ArchArchetype type ) {
		type ??= new ArchArchetype();

		return new ArchArchetype {
			Version = type.Version,
			Name = type.Name,
			Title = type.Title,
			Description = type.Description,
			Icon = type.Icon,
			Openings = new List<string>( type.Openings ),
			Pillars = new List<string>( type.Pillars ),
			Shell = Copy( type.Shell ),
			Wing = Copy( type.Wing ),
			WingRoof = type.WingRoof,
			WingEaveDrop = type.WingEaveDrop,
			Shows = new List<ArchOptionGroup>( type.Shows ),
			DriveWidth = type.DriveWidth,
			DriveSplay = type.DriveSplay,
			DriveKind = type.DriveKind,
			MinimumPlot = type.MinimumPlot,
			Steps = type.Steps
		};
	}

	static ArchSectionRules Copy( ArchSectionRules rules ) {
		rules ??= new ArchSectionRules();

		var result = new ArchSectionRules {
			WallHeight = rules.WallHeight,
			Roof = rules.Roof,
			Ridge = rules.Ridge,
			RoofPitch = rules.RoofPitch,
			Overhang = rules.Overhang,
			Floor = rules.Floor,
			Foundation = rules.Foundation,
			Ceiling = rules.Ceiling,
			Gutters = rules.Gutters,
			Fascia = rules.Fascia,
			Soffit = rules.Soffit,
			Frame = rules.Frame,
			FrameSpacing = rules.FrameSpacing,
			FloorBoards = rules.FloorBoards,
			FloorBoardYaw = rules.FloorBoardYaw,
			Wall = rules.Wall is null ? null : ArchWallPreset.Copy( rules.Wall ),
			Parapet = rules.Parapet,
			ParapetHeight = rules.ParapetHeight
		};

		ArchArchetypeRules.Skin( result.Palette, rules.Palette );

		return result;
	}
}