Editor/Tool/Subtools/ArchWindowSubtool.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;

namespace Sunless.Architecture;

// A sash unit is authored, not just a hole - hence the separate tool.
[Title( "Window" ), Icon( "window" ), Group( "06" )]
public sealed class ArchWindowSubtool( ArchTool owner ) : ArchOpeningPlacingSubtool( owner ) {
	public override ArchSurface[] Surfaces => new[] { ArchSurface.Glass, ArchSurface.WindowFrame, ArchSurface.WindowSash, ArchSurface.Sill, ArchSurface.Trim, ArchSurface.Reveal };

	float width;
	float? startHeight;
	float? endHeight;
	bool? broken;
	bool? breakable;
	OpeningFurniture? furniture;
	ArchOpeningPreset custom;

	protected override string Title() => "Window";

	protected override string Advice() => "Click a wall, or drag a frame.";

	protected override string Label( ArchOpeningPreset preset, ArchOpeningPreset unit ) => Describe( unit );

	protected override ArchOpeningPreset Shape( ArchOpeningPreset preset ) {
		var firstHeight = startHeight ?? preset.SillHeight;
		var secondHeight = endHeight ?? preset.SillHeight + preset.Height;
		var bottom = MathF.Min( firstHeight, secondHeight );
		var top = MathF.Max( firstHeight, secondHeight );

		var shaped = preset.Copy();

		shaped.Kind = OpeningKind.Window;
		shaped.Width = width > 0f ? width : preset.Width;
		shaped.Height = MathF.Max( ArchGridService.FinestSize, top - bottom );
		shaped.SillHeight = bottom;
		shaped.Leaf = false;
		shaped.StartOpen = false;
		shaped.Glass = Broken( preset ) ? GlassState.Broken : GlassState.Intact;
		shaped.Breakable = Breaks( preset );
		shaped.Furniture = Fitted( preset );

		return shaped;
	}

	static string Describe( ArchOpeningPreset unit ) {
		var lights = unit.Lights > 1 ? $"{unit.Lights} lights, " : "";
		var panes = unit.PanesAcross * unit.PanesDown;

		return unit.Glazed
			? $"{unit.Name} — {lights}{panes} over {panes}"
			: $"{unit.Name} — {unit.Width:0}x{unit.Height:0}";
	}

	protected override ArchOpeningPreset Preset() {
		return custom ?? Owner.Kit.FindOpening( Owner.ActiveWindow ) ?? Windows( Owner.Kit ).FirstOrDefault();
	}

	static List<ArchOpeningPreset> Windows( ArchKit kit ) {
		return kit.Openings.Where( preset => preset.Kind == OpeningKind.Window ).ToList();
	}

	// Which unit, what is bolted over it, what state its glass is in, then how big it is - picked steps first,
	// so the sequence carries itself as far as the toggles and the typed sizes, which can never answer.
	//
	// Lights, sashes, panes and casing are not here. They are what a WINDOW IS - the card just picked already
	// says "2 lights, 4 over 4" on it - so they are cut in the Window Designer behind the shelf's design chip,
	// and re-cut on a standing unit through its own sheet in Select. Kit glazing materials are not here either:
	// they skin every window in the map, not the one about to be dragged.
	protected override void BuildOptions( ToolSidebarWidget panel ) {
		var chosen = Preset();

		using var flow = ArchWorkflow.In( panel, Scope( "flow" ), Refresh );

		flow.Step( "Window", chosen?.Name ?? "None", Glyph( chosen ), step => Browse( panel, step ) );

		flow.Step( "Fitted", ArchOpeningUi.Name( Fitted( chosen ) ), ArchOpeningUi.Glyph( Fitted( chosen ) ), PickFitted );

		if ( chosen?.IsGlazed == true ) {
			flow.Step( "Glass", GlassName( chosen ), GlassGlyph( chosen ), BuildGlass );
		}

		flow.Step( "Placement", PlacementName( chosen ), "straighten", BuildPlacement );
	}

	static string Glyph( ArchOpeningPreset preset ) => preset is null ? "window" : ArchIcons.OpeningGlyph( preset );

	OpeningFurniture Fitted( ArchOpeningPreset preset ) => furniture ?? preset?.Furniture ?? OpeningFurniture.None;

	bool Broken( ArchOpeningPreset preset ) => broken ?? preset?.Glass == GlassState.Broken;

	bool Breaks( ArchOpeningPreset preset ) => breakable ?? preset?.Breakable == true;

	void PickFitted( ArchWorkflowStep step ) {
		using var grid = ArchIconGrid.In( step.Layout );

		ArchOpeningUi.Furniture( grid, OpeningKind.Window, Fitted( Preset() ), value => Fit( value, step ) );
	}

	void Fit( OpeningFurniture fitted, ArchWorkflowStep step ) {
		furniture = fitted;

		step.Chose();
	}

	string GlassName( ArchOpeningPreset preset ) {
		var state = Broken( preset ) ? "Broken" : "Intact";

		return Breaks( preset ) ? $"{state}, breakable" : state;
	}

	string GlassGlyph( ArchOpeningPreset preset ) => Broken( preset ) ? "broken_image" : Breaks( preset ) ? "shield" : "window";

	// Two states of the same panes rather than one three-way answer: what is left of a broken window is still
	// glass that can be shot out.
	void BuildGlass( ArchWorkflowStep step ) {
		var chosen = Preset();

		using var grid = ArchIconGrid.In( step.Layout );

		grid.Toggle( "Already broken", "opt_broken", "broken_image", Broken( chosen ), value => broken = value );
		grid.Toggle( "Breakable — panes on their own object", "opt_breakable", "shield", Breaks( chosen ), value => breakable = value );
	}

	// What the ghost will read, so the closed row and the released drag agree.
	string PlacementName( ArchOpeningPreset preset ) {
		if ( preset is null ) {
			return "";
		}

		var unit = Shape( preset );

		return $"{unit.Width:0} x {unit.Height:0} at {unit.SillHeight:0}";
	}

	void BuildPlacement( ArchWorkflowStep step ) {
		var chosen = Preset();

		step.Layout.Add( ArchPartUi.OptionalNumber( "Start height", startHeight, chosen?.SillHeight ?? 0f, value => startHeight = value ) );
		step.Layout.Add( ArchPartUi.OptionalNumber( "End height", endHeight, chosen?.SillHeight + chosen?.Height ?? 0f, value => endHeight = value ) );
		step.Layout.Add( ArchPartUi.Number( "Width", width, chosen?.Width ?? 0f, value => width = value ) );

		ArchOpeningDragUi.DragOptions( step.Layout, placement.Drag );
	}

	// The shelf and the designer hang off the SIDEBAR rather than off the step, because a step's widget is torn
	// down on the next refresh and a modal parented to one would go with it.
	void Browse( ToolSidebarWidget panel, ArchWorkflowStep step ) {
		var shelf = new ArchPresetBrowser<ArchOpeningPreset>( panel, Owner.Kit, Scope( "presets" ), "Windows" ) {
			Chosen = preset => string.Equals( Owner.ActiveWindow, preset.Name, StringComparison.OrdinalIgnoreCase ),
			Choose = preset => Use( preset, step ),
			Design = () => Author( panel, step ),
			Exports = "window"
		};

		shelf.DesignButton.Visible = true;
		shelf.DesignButton.ToolTip = "Design a window preset";
		shelf.Set( Offered() );

		step.Add( shelf );
	}

	List<ArchPresetItem<ArchOpeningPreset>> Offered() {
		return Owner.Offered( preset => preset.Kind == OpeningKind.Window )
			.Select( preset => new ArchPresetItem<ArchOpeningPreset> {
				Value = preset,
				Name = preset.Name,
				Detail = ArchOpeningStage.Describe( preset ),
				Identity = ArchOpeningStage.Identity( preset ),
				Category = Family( preset ),
				Badge = preset.Glass == GlassState.Broken ? "broken" : null,
				Glyph = ArchIcons.OpeningGlyph( preset ),
				Tags = $"{preset.Lights} lights {preset.Sashes} sashes {preset.PanesAcross}x{preset.PanesDown} panes",
				Recommended = Owner.Recommends( preset ),
				View = ArchPresetPreview.Elevation,
				Interest = ArchOpeningStage.Interest( preset, Owner.Kit ),
				Recipe = () => ArchOpeningStage.Of( preset, Owner.Kit )
			} )
			.ToList();
	}

	static string Family( ArchOpeningPreset preset ) {
		if ( preset.Glass == GlassState.Broken ) {
			return "Damaged";
		}

		if ( preset.Sashes > 1 ) {
			return "Sash";
		}

		return preset.Width > preset.Height ? "Wide" : "Tall";
	}

	void Use( ArchOpeningPreset preset, ArchWorkflowStep step ) {
		custom = null;
		Owner.ActiveWindow = preset.Name;

		Inherit();

		step.Chose();
	}

	void Author( Widget panel, ArchWorkflowStep step ) {
		var initial = Preset() is { } preset
			? Shape( preset )
			: new ArchOpeningPreset { Kind = OpeningKind.Window, Glazed = true, HasSill = true };

		new ArchOpeningDesigner( panel, Owner.Kit, initial, true, designed => ApplyDesigned( designed, step ) ).Show();
	}

	// Every override drops back to the preset's own value: a number typed against the last window is not
	// an opinion about this one.
	void Inherit() {
		width = 0f;
		startHeight = null;
		endHeight = null;
		broken = null;
		breakable = null;
		furniture = null;
	}

	void ApplyDesigned( ArchOpeningPreset preset, ArchWorkflowStep step ) {
		custom = ArchOpeningDesigner.Copy( preset );
		Owner.RegisterOpeningPreset( ArchOpeningDesigner.Copy( preset ) );
		Owner.ActiveWindow = preset.Name;

		Inherit();

		ArchPresetPreview.Invalidate( ArchOpeningStage.Identity( preset ) );

		step.Chose();
	}
}