Editor/Wall/ArchWallUi.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;

namespace Sunless.Architecture;

// Read by Wall, Select and both designer forms off the same IArchWallTreatment.
public static class ArchWallUi {
	// Not every ArchSurface - most aren't wall at all; nobody could choose from all.
	static readonly ArchSurface[] Fields =
	{
		ArchSurface.WallBase,
		ArchSurface.Foundation,
		ArchSurface.WallExterior,
		ArchSurface.Panel,
		ArchSurface.Siding,
		ArchSurface.Pillar
	};

	// What a thin proud strip is worth being made of.
	static readonly ArchSurface[] Strips =
	{
		ArchSurface.Trim,
		ArchSurface.Gutter,
		ArchSurface.Railing,
		ArchSurface.WallCap,
		ArchSurface.WallBase,
		ArchSurface.Kerb
	};

	// A creation sidebar puts height and thickness in Placement, where the next drag reads them; every
	// other caller is editing a treatment whole and wants them here. A rhythm is spaced over a real
	// elevation, so only a caller holding one offers it.
	public static void Build( ToolSidebarWidget panel, ArchKit kit, IArchWallTreatment wall, Action refresh, Action changed, bool sizes = true, bool openings = true ) {
		if ( sizes ) {
			Sizes( panel, kit, wall, changed );
		}

		Faces( panel, wall, changed );
		Head( panel, kit, wall, refresh, changed );
		Cladding( panel, wall, changed );
		Bands( panel, kit, wall, refresh, changed );
		Trims( panel, kit, wall, refresh, changed );

		if ( openings ) {
			Rhythm( panel, kit, wall, refresh, changed );
		}

		Pilasters( panel, wall, refresh, changed );
	}

	static void Sizes( ToolSidebarWidget panel, ArchKit kit, IArchWallTreatment wall, Action changed ) {
		var group = panel.AddGroup( "Wall" );

		group.Add( ArchPartUi.Number( "Height", wall.Height, kit.WallHeight, value => wall.Height = value, changed ) );
		group.Add( ArchPartUi.Number( "Thickness", wall.Thickness, kit.WallThickness, value => wall.Thickness = value, changed ) );
	}

	// Which skin each face takes, whether the far one exists at all, and what lines them.
	public static void Faces( ToolSidebarWidget panel, IArchWallTreatment wall, Action changed ) {
		using var grid = ArchIconGrid.In( panel.AddGroup( "Faces" ) );

		grid.Toggle( "Exterior — skinned outside as well as in", "opt_exterior", "villa", wall.Exterior,
			value => { wall.Exterior = value; changed?.Invoke(); } );

		grid.Toggle( "Single-sided — the far face is never emitted, for a silhouette nobody enters",
			"opt_single_sided", "filter_1", wall.SingleSided,
			value => { wall.SingleSided = value; changed?.Invoke(); } );

		grid.Toggle( "Skirting round the room", "opt_baseboard", "border_bottom", wall.Baseboard,
			value => { wall.Baseboard = value; changed?.Invoke(); } );

		grid.Toggle( "Wainscot up the inside face", "opt_wainscot", "table_rows", wall.Wainscot,
			value => { wall.Wainscot = value; changed?.Invoke(); } );
	}

	// ONE question with three answers, because that is what the generator already does with them: a cornice style
	// takes the plain cap over, so a head is bare, coped or crowned and never two of those at once. It was a
	// toggle in Faces, a Coping group and a Cornice group - three rows that could contradict each other.
	//
	// Which stack, once crowned, stays the picker's: every dimension a cornice has was proportioned together in
	// the style, so offering them as rows would be offering an author six ways to get it wrong.
	public static void Head( ToolSidebarWidget panel, ArchKit kit, IArchWallTreatment wall, Action refresh, Action changed ) {
		var crowned = Crowning( kit, wall ) is not null;
		var stocked = ArchCorniceUi.Offered( kit );
		var group = panel.AddGroup( "Head" );

		using ( var grid = ArchIconGrid.In( group ) ) {
			grid.Pick( "Bare — the wall runs up to whatever it meets", "head_bare", "block",
				!crowned && !wall.Cap, () => Crown( wall, false, "", refresh, changed ) );

			grid.Pick( "Coping — a plain cap oversailing both faces, for a parapet or a wall standing clear",
				"head_coping", "border_top", !crowned && wall.Cap, () => Crown( wall, true, "", refresh, changed ) );

			// Absent rather than dead where a kit stocks no cornice - a tile that cannot light reads as broken.
			if ( stocked.Length > 0 ) {
				grid.Pick( "Cornice — a crowned stack hung from the head", "head_cornice", "view_stream",
					crowned, () => Crown( wall, false, stocked, refresh, changed ) );
			}
		}

		if ( crowned ) {
			group.Add( ArchCorniceUi.Picker( panel, kit, "wall.cornices", () => wall.Cornice,
				name => { wall.Cornice = name; changed?.Invoke(); refresh?.Invoke(); } ) );

			return;
		}

		if ( !wall.Cap ) {
			return;
		}

		// The coping is a proud section like a band course, so it carries the same two dimensions.
		group.Add( ArchPartUi.Number( "Cap height", wall.CapHeight, kit.WallCapHeight, value => wall.CapHeight = value, changed ) );
		group.Add( ArchPartUi.Number( "Oversails by", wall.CapOverhang, kit.CasingDepth, value => wall.CapOverhang = value, changed ) );
	}

	static void Crown( IArchWallTreatment wall, bool cap, string cornice, Action refresh, Action changed ) {
		wall.Cap = cap;
		wall.Cornice = cornice;

		changed?.Invoke();
		refresh?.Invoke();
	}

	public static ArchCorniceStyle Crowning( ArchKit kit, IArchWallTreatment wall ) => kit.FindCornice( wall.Cornice );

	static void Cladding( ToolSidebarWidget panel, IArchWallTreatment wall, Action changed ) {
		using var grid = ArchIconGrid.In( panel.AddGroup( "Cladding" ) );

		foreach ( var value in Enum.GetValues<WallCladding>() ) {
			var captured = value;

			grid.Pick( Describe( captured ), $"cladding_{captured}".ToLowerInvariant(), Glyph( captured ),
				wall.Cladding == captured, () => { wall.Cladding = captured; changed?.Invoke(); } );
		}
	}

	// A band per row - a real elevation changes field more than once.
	static void Bands( ToolSidebarWidget panel, ArchKit kit, IArchWallTreatment wall, Action refresh, Action changed ) {
		var group = panel.AddGroup( wall.Bands.Count == 0 ? "Bands" : $"Bands ({wall.Bands.Count})" );
		var storey = Storey( kit, wall );

		foreach ( var band in ArchWallTreatments.Ordered( wall.Bands, storey ).ToList() ) {
			var captured = band;

			group.Add( ArchPartUi.Check( "Rides at the wall head — a cap", captured.AtHead,
				value => { captured.AtHead = value; changed?.Invoke(); refresh?.Invoke(); }, changed ) );

			if ( !captured.AtHead ) {
				group.Add( ArchPartUi.Number( "Changes at", captured.Height, 0f, value => captured.Height = value, changed ) );
			}

			using ( var fields = ArchIconGrid.In( group ) ) {
				foreach ( var role in Fields ) {
					var wanted = role;

					fields.Pick( $"The field below it is {wanted}", $"surface_{wanted}".ToLowerInvariant(), FieldGlyph( wanted ),
						captured.Field == wanted, () => { captured.Field = wanted; changed?.Invoke(); } );
				}
			}

			group.Add( ArchPartUi.Check( "Trim standing proud on the change", captured.Course,
				value => { captured.Course = value; changed?.Invoke(); }, changed ) );

			// Height AND depth - a cap is thicker, finishing the field rather than dividing two.
			group.Add( ArchPartUi.Number( "Trim height", captured.CourseHeight, kit.WallBandHeight,
				value => captured.CourseHeight = value, changed ) );

			group.Add( ArchPartUi.Number( "Stands proud", captured.CourseDepth, kit.WallBandDepth,
				value => captured.CourseDepth = value, changed ) );

			group.Add( ArchPartUi.Check( "Dentil blocks under it", captured.Dentils,
				value => { captured.Dentils = value; changed?.Invoke(); }, changed ) );

			using ( var seats = ArchIconGrid.In( group ) ) {
				seats.Pick( "Straddles the change — a string course between two fields", "band_centred", "horizontal_split",
					captured.Seat == BandSeat.Centred, () => { captured.Seat = BandSeat.Centred; changed?.Invoke(); } );

				seats.Pick( "Sits on the field below — a capping", "band_above", "vertical_align_top",
					captured.Seat == BandSeat.Above, () => { captured.Seat = BandSeat.Above; changed?.Invoke(); } );
			}

			using var remove = ArchIconGrid.In( group );

			remove.Toggle( $"Remove the band at {captured.At( storey ):0}", "band_remove", "delete", false, _ => {
				wall.Bands.Remove( captured );
				changed?.Invoke();
				refresh?.Invoke();
			} );
		}

		using var add = ArchIconGrid.In( group );

		add.Toggle( "Add a band — the field below it takes its own role", "band_add", "add", false, _ => {
			wall.Bands.Add( new ArchWallBand { Height = Next( kit, wall ) } );
			changed?.Invoke();
			refresh?.Invoke();
		} );
	}

	static void Trims( ToolSidebarWidget panel, ArchKit kit, IArchWallTreatment wall, Action refresh, Action changed ) {
		var group = panel.AddGroup( wall.Trims.Count == 0 ? "Trim bands" : $"Trim bands ({wall.Trims.Count})" );
		var storey = Storey( kit, wall );

		foreach ( var trim in ArchWallTreatments.Standing( wall.Trims, storey ).ToList() ) {
			var captured = trim;

			group.Add( ArchPartUi.Check( "Rides at the wall head", captured.AtHead,
				value => { captured.AtHead = value; changed?.Invoke(); refresh?.Invoke(); }, changed ) );

			if ( !captured.AtHead ) {
				group.Add( ArchPartUi.Number( "Sits at", captured.Height, 96f, value => captured.Height = value, changed ) );
			}

			group.Add( ArchPartUi.Number( "Band height", captured.Rise, 3f, value => captured.Rise = value, changed ) );
			group.Add( ArchPartUi.Number( "Stands proud", captured.Depth, 1.5f, value => captured.Depth = value, changed ) );
			group.Add( ArchPartUi.Number( "Bevel", captured.Bevel, 0.75f, value => captured.Bevel = value, changed ) );

			using ( var fields = ArchIconGrid.In( group ) ) {
				foreach ( var role in Strips ) {
					var wanted = role;

					fields.Pick( $"Skinned as {wanted}", $"surface_{wanted}".ToLowerInvariant(), FieldGlyph( wanted ),
						captured.Field == wanted, () => { captured.Field = wanted; changed?.Invoke(); } );
				}
			}

			using var row = ArchIconGrid.In( group );

			row.Toggle( "Runs on the inside face instead", "trim_interior", "flip_to_back", captured.Interior,
				value => { captured.Interior = value; changed?.Invoke(); } );

			row.Toggle( $"Remove the band at {captured.At( storey ):0}", "trim_remove", "delete", false, _ => {
				wall.Trims.Remove( captured );
				changed?.Invoke();
				refresh?.Invoke();
			} );
		}

		using var add = ArchIconGrid.In( group );

		add.Toggle( "Add a proud band — anywhere up the wall, in any role", "trim_add", "add", false, _ => {
			wall.Trims.Add( new ArchWallTrim { Height = NextTrim( kit, wall ) } );
			changed?.Invoke();
			refresh?.Invoke();
		} );
	}

	// The rhythm the elevation wears. The units it stands are real openings, so the drag stays the way to place
	// one that breaks the pattern - the rhythm parts around whatever is already there.
	static void Rhythm( ToolSidebarWidget panel, ArchKit kit, IArchWallTreatment wall, Action refresh, Action changed ) {
		var run = wall.OpeningRun;

		using ( var grid = ArchIconGrid.In( panel.AddGroup( "Openings" ) ) ) {
			grid.Toggle( "Wear a rhythm of openings the whole elevation shares, at a bay", "opt_opening_run", "window", run.Stands,
				value => {
					run.Preset = value ? Offered( kit ) : "";
					run.Bay = value ? 168f : 0f;
					changed?.Invoke();
					refresh?.Invoke();
				} );
		}

		if ( !run.Stands ) {
			return;
		}

		var group = panel.AddGroup( "Bay rhythm" );
		var unit = kit.Openings.FirstOrDefault( preset => string.Equals( preset.Name, run.Preset, StringComparison.OrdinalIgnoreCase ) );

		group.Add( Units( panel, kit, run, changed ) );
		group.Add( ArchPartUi.Number( "Bay at most", run.Bay, 168f, value => run.Bay = value, changed ) );
		group.Add( ArchPartUi.Integer( "Or exactly this many", run.Count, 0, value => run.Count = value, changed ) );
		group.Add( ArchPartUi.Number( "Width", run.Width, unit?.Width ?? 0f, value => run.Width = value, changed ) );
		group.Add( ArchPartUi.Number( "Height", run.Height, unit?.Height ?? 0f, value => run.Height = value, changed ) );
		group.Add( ArchPartUi.Number( "Sill height", run.SillHeight, unit?.SillHeight ?? 0f, value => run.SillHeight = value, changed ) );
	}

	static Widget Units( Widget parent, ArchKit kit, ArchOpeningRun run, Action changed ) {
		var browser = new ArchPresetBrowser<ArchOpeningPreset>( parent, kit, "wall.rhythm.presets", "Unit" ) {
			Chosen = preset => string.Equals( run.Preset, preset.Name, StringComparison.OrdinalIgnoreCase ),
			Choose = preset => {
				run.Preset = preset.Name;
				changed?.Invoke();
			}
		};

		browser.Set( kit.Openings
			.Select( preset => new ArchPresetItem<ArchOpeningPreset> {
				Value = preset,
				Name = preset.Name,
				Detail = ArchOpeningStage.Describe( preset ),
				Identity = ArchOpeningStage.Identity( preset ),
				Category = preset.Kind.ToString(),
				Glyph = ArchIcons.OpeningGlyph( preset ),
				Tags = preset.Kind.ToString(),
				Recommended = preset.Kind == OpeningKind.Window,
				View = ArchPresetPreview.Elevation,
				Interest = ArchOpeningStage.Interest( preset, kit ),
				Recipe = () => ArchOpeningStage.Of( preset, kit )
			} )
			.ToList() );

		return browser;
	}

	// A rhythm is windows far more often than it is anything else, so switching it on offers one.
	static string Offered( ArchKit kit ) {
		var wanted = kit.Openings.FirstOrDefault( preset => preset.Kind == OpeningKind.Window )
			?? kit.Openings.FirstOrDefault();

		return wanted?.Name ?? "";
	}

	static void Pilasters( ToolSidebarWidget panel, IArchWallTreatment wall, Action refresh, Action changed ) {
		var run = wall.Pilasters;

		using ( var grid = ArchIconGrid.In( panel.AddGroup( "Pilasters" ) ) ) {
			grid.Toggle( "Express the column line outside the cladding, at a bay", "opt_pilasters", "view_week", run.Stands,
				value => { run.Bay = value ? 168f : 0f; changed?.Invoke(); refresh?.Invoke(); } );
		}

		if ( !run.Stands ) {
			return;
		}

		var group = panel.AddGroup( "Column line" );

		group.Add( ArchPartUi.Number( "Bay at most", run.Bay, 168f, value => run.Bay = value, changed ) );
		group.Add( ArchPartUi.Integer( "Or exactly this many", run.Count, 0, value => run.Count = value, changed ) );
		group.Add( ArchPartUi.Number( "Strip width", run.Width, 12f, value => run.Width = value, changed ) );
		group.Add( ArchPartUi.Number( "Stands proud", run.Depth, 3f, value => run.Depth = value, changed ) );
		group.Add( ArchPartUi.Number( "Starts above the foot", run.Foot, 0f, value => run.Foot = value, changed ) );
		group.Add( ArchPartUi.Number( "Stops short of the head", run.Headroom, 0f, value => run.Headroom = value, changed ) );
		group.Add( ArchPartUi.Number( "Climbs past the head", run.Rise, 0f, value => run.Rise = value, changed ) );
		group.Add( ArchPartUi.Number( "Rusticated course", run.Course, 0f, value => run.Course = value, changed ) );
		group.Add( ArchPartUi.Number( "Joint between courses", run.Joint, 0f, value => run.Joint = value, changed ) );
		group.Add( ArchPartUi.Number( "Plinth block", run.Plinth, 0f, value => run.Plinth = value, changed ) );
		group.Add( ArchPartUi.Number( "Cap block", run.Cap, 0f, value => run.Cap = value, changed ) );

		using var corners = ArchIconGrid.In( group );

		corners.Toggle( "End strips wrap the corner instead of being held inboard of it", "pilaster_corners", "border_outer",
			run.OnCorners, value => { run.OnCorners = value; changed?.Invoke(); } );
	}

	static float Storey( ArchKit kit, IArchWallTreatment wall ) => wall.Height > 1f ? wall.Height : kit.WallHeight;

	// Clear of the one below, so three read as stripes, on a grid line not a fraction.
	static float NextTrim( ArchKit kit, IArchWallTreatment wall ) {
		var height = Storey( kit, wall );
		var standing = ArchWallTreatments.Standing( wall.Trims, height ).LastOrDefault();
		var grid = new ArchGridService();

		var wanted = standing is null
			? height * 0.4f
			: MathF.Min( standing.At( height ) + standing.Rise + height * 0.15f, height * 0.92f );

		return MathF.Max( grid.SubgridSize(), grid.Subgrid( wanted ) );
	}

	// Above the highest band, or at dado height on a bare wall.
	static float Next( ArchKit kit, IArchWallTreatment wall ) {
		var height = Storey( kit, wall );
		var standing = ArchWallTreatments.Ordered( wall.Bands, height ).LastOrDefault();

		return standing is null
			? ArchGridService.Fine( height * 0.34f )
			: ArchGridService.Fine( MathF.Min( standing.At( height ) + height * 0.25f, height * 0.9f ) );
	}

	public static string Describe( WallCladding cladding ) => cladding switch {
		WallCladding.Weatherboard => "Weatherboard — lapped horizontal courses",
		WallCladding.Profiled => "Profiled sheeting — vertical trapezoidal ribs",
		_ => "None — the field is flat"
	};

	static string Glyph( WallCladding cladding ) => cladding switch {
		WallCladding.Weatherboard => "view_stream",
		WallCladding.Profiled => "view_week",
		_ => "crop_square"
	};

	static string FieldGlyph( ArchSurface field ) => field switch {
		ArchSurface.Foundation => "foundation",
		ArchSurface.WallBase => "grid_4x4",
		ArchSurface.Panel => "crop_square",
		ArchSurface.Siding => "view_stream",
		ArchSurface.Pillar => "view_column",
		ArchSurface.Trim => "horizontal_rule",
		ArchSurface.Gutter => "water_damage",
		ArchSurface.Railing => "fence",
		ArchSurface.WallCap => "border_top",
		ArchSurface.Kerb => "square_foot",
		_ => "villa"
	};

}