Editor/Stair/ArchStairWindow.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Editor;

namespace Sunless.Architecture;

public sealed class ArchStairWindow : Window {
	static ArchStairWindow standing;

	readonly ArchTool tool;
	readonly int stairId;

	const string LiveKey = "Arch.Stair.Live";
	const string WalkKey = "Arch.Stair.OpenOnWalk";
	const string Climb = "The climb";
	const string Whole = "The stair";
	const int PanelWidth = 260;

	ArchStairCanvas canvas;
	Widget climbing;
	ScrollArea settings;
	ToolSidebarWidget sheet;
	Layout steps;
	Label caption;
	SegmentedControl turns;
	ArchNumberEdit angle;
	Button build;

	bool live = EditorCookie.Get( LiveKey, false );

	int segments = 6;
	float sweep = 90f;

	public static void Open( ArchTool tool, ArchStairPart stair ) {
		if ( tool is null || stair is null ) {
			return;
		}

		standing?.Close();

		standing = new ArchStairWindow( tool, stair );
		standing.Show();
	}

	public static void OpenOnWalk( ArchTool tool, ArchStairPart stair ) {
		if ( !EditorCookie.Get( WalkKey, true ) ) {
			return;
		}

		Open( tool, stair );
	}

	ArchStairWindow( ArchTool tool, ArchStairPart stair ) {
		this.tool = tool;
		stairId = stair.Id;

		DeleteOnClose = true;
		WindowTitle = $"Stair — {stair.Name}";
		Size = new Vector2( 980, 640 );
		MinimumSize = new Vector2( 760, 480 );

		Canvas = new Widget( this ) { Layout = Layout.Row() };
		Canvas.Layout.Margin = 8;
		Canvas.Layout.Spacing = 8;

		BuildDrawing();
		BuildPanel();

		Rebuild();
	}

	ArchStairPart Subject() {
		return tool?.Plan?.Parts<ArchStairPart>().FirstOrDefault( part => part.Id == stairId );
	}

	void BuildDrawing() {
		var column = Canvas.Layout.AddColumn( 1 );

		column.Spacing = 6;

		var views = column.AddRow();

		views.Spacing = 4;

		var panes = new SegmentedControl( this );

		foreach ( var view in Enum.GetValues<ArchStairView>() ) {
			panes.AddOption( Describe( view ), Glyph( view ) );
		}

		panes.OnSelectedChanged = name => {
			canvas.View = Enum.GetValues<ArchStairView>().First( view => Describe( view ) == name );
			canvas.Update();
		};

		views.Add( panes );
		views.AddStretchCell();

		var profile = new Checkbox( "Profile" ) { Value = true };

		profile.Toggled += () => {
			canvas.Profile = profile.Value;
			canvas.Update();
		};

		views.Add( profile );

		var surrounds = new Checkbox( "Building" ) { Value = true };

		surrounds.Toggled += () => {
			canvas.Surrounds = surrounds.Value;
			canvas.Update();
		};

		views.Add( surrounds );

		var walked = new Checkbox( "Opens on a walk" ) { Value = EditorCookie.Get( WalkKey, true ) };

		walked.Toggled += () => EditorCookie.Set( WalkKey, walked.Value );

		views.Add( walked );

		var living = new Checkbox( "Live" ) { Value = live };

		living.Toggled += () => {
			live = living.Value;
			EditorCookie.Set( LiveKey, live );

			if ( live ) {
				Build();
			}
		};

		views.Add( living );

		build = new Button.Primary( "Build", "refresh" ) { Clicked = Build };
		views.Add( build );

		caption = new Label( "" );
		views.Add( caption );

		canvas = new ArchStairCanvas( this, tool, Subject, Preview, Commit ) {
			Selected = _ => RebuildStack()
		};

		column.Add( canvas, 1 );
		column.Add( new Label( "Drag a step's middle to move it — the steps around it give way — its head to lengthen "
			+ "it, a flank to widen it, the pink pin past its head to aim it, and the green pin in an elevation to pin "
			+ "its climb. Click a step in the plan or the profile to work on it." ) { WordWrap = true } );
	}

	static string Describe( ArchStairView view ) => view switch {
		ArchStairView.Along => "Elevation",
		ArchStairView.Across => "Section",
		_ => "Plan"
	};

	static string Glyph( ArchStairView view ) => view switch {
		ArchStairView.Along => "crop_landscape",
		ArchStairView.Across => "flip",
		_ => "grid_on"
	};

	static string Describe( StairTurn turn ) => turn switch {
		StairTurn.Left => "Left",
		StairTurn.Right => "Right",
		StairTurn.Back => "Back",
		_ => "Straight"
	};

	static string Glyph( StairTurn turn ) => turn switch {
		StairTurn.Left => "turn_left",
		StairTurn.Right => "turn_right",
		StairTurn.Back => "u_turn_left",
		_ => "straight"
	};

	void BuildPanel() {
		var column = Canvas.Layout.AddColumn();

		column.Spacing = 6;

		var pages = new SegmentedControl( this );

		pages.AddOption( Climb, "stairs" );
		pages.AddOption( Whole, "tune" );
		pages.OnSelectedChanged = name => Show( name == Climb );

		column.Add( pages );

		climbing = new Widget( this ) { Layout = Layout.Column() };
		climbing.Layout.Spacing = 6;
		climbing.MinimumWidth = PanelWidth;
		climbing.MaximumWidth = PanelWidth;

		BuildClimb( climbing.Layout );

		column.Add( climbing, 1 );

		settings = new ScrollArea( this );
		sheet = new ToolSidebarWidget( settings );
		settings.Canvas = sheet;
		settings.MinimumWidth = PanelWidth;
		settings.MaximumWidth = PanelWidth;
		settings.Visible = false;

		column.Add( settings, 1 );
	}

	void Show( bool climb ) {
		climbing.Visible = climb;
		settings.Visible = !climb;

		if ( !climb ) {
			BuildSheet();
		}
	}

	// Same sheet the sidebar uses — one set of controls, two places to stand
	void BuildSheet() {
		if ( Subject() is not { } stair ) {
			return;
		}

		sheet.Layout.Clear( true );

		ArchStairUi.Build( sheet, stair, BuildSheet, Commit );
		ArchStairUi.Sizes( sheet, stair, Commit );

		sheet.Layout.AddStretchCell();
	}

	void BuildClimb( Layout column ) {
		var scroll = new ScrollArea( this );

		scroll.Canvas = new Widget( scroll ) { Layout = Layout.Column() };
		scroll.Canvas.Layout.Spacing = 2;

		steps = scroll.Canvas.Layout;
		column.Add( scroll, 1 );

		var actions = column.AddRow();

		actions.Spacing = 4;
		actions.Add( new Button( "Flight", "stairs" ) { Clicked = AddFlight } );
		actions.Add( new Button( "Platform", "horizontal_rule" ) { Clicked = AddPlatform } );
		actions.Add( new Button( "Railing", "fence" ) { Clicked = AddRailing } );

		column.Add( new Label( "Turn this step off the one below it" ) );

		turns = new SegmentedControl( this );

		foreach ( var turn in Enum.GetValues<StairTurn>() ) {
			turns.AddOption( Describe( turn ), Glyph( turn ) );
		}

		turns.OnSelectedChanged = name =>
			Turn( Enum.GetValues<StairTurn>().First( turn => Describe( turn ) == name ) );

		column.Add( turns );

		var aiming = column.AddRow();

		aiming.Spacing = 4;
		aiming.Add( new Label( "Angle" ) );

		// Under the list, not in the row — row rebuilds on click would eat the caret
		angle = new ArchNumberEdit( "", ArchGridService.FinestSize, 1f, "0.##", Bend ) {
			PlaceholderText = "0"
		};

		angle.TextEdited += text => Bend( float.TryParse( text, out var typed ) ? typed : 0f );

		aiming.Add( angle );

		var cutting = column.AddRow();

		cutting.Spacing = 4;
		cutting.Add( new Button( "Segment", "content_cut" ) { Clicked = SegmentFlight } );
		cutting.Add( new Button( "Delete", "backspace" ) { Clicked = DeleteStep } );

		var curving = column.AddRow();

		curving.Spacing = 4;
		curving.Add( ArchPartUi.Integer( "Curve", segments, 6, value => segments = Math.Clamp( value, 2, 32 ) ) );
		curving.Add( ArchPartUi.Number( "°", sweep, 90f, value => sweep = value ) );
		curving.Add( new Button( "Curve", "turn_slight_right" ) { Clicked = CurveFlight } );
	}

	void RebuildStack() {
		steps.Clear( true );

		if ( Subject() is not { } stair ) {
			return;
		}

		var climbs = ArchStairLanes.Climbs( stair.Core, stair.Lanes );

		for ( var index = stair.Lanes.Count - 1; index >= 0; index-- ) {
			steps.Add( Row( stair, index, climbs[index] ) );
		}

		steps.AddStretchCell();

		ShowStep( stair );
	}

	void ShowStep( ArchStairPart stair ) {
		var index = canvas.Picked;
		var joint = index > 0 && index < stair.Lanes.Count;

		turns.Enabled = joint;

		if ( joint ) {
			turns.Selected = Describe( stair.Lanes[index - 1].Walk.Between( stair.Lanes[index].Walk ) );
		}

		var flight = Held( stair ) is { Climbs: true } lane ? lane : null;

		angle.Enabled = flight is not null;
		angle.Text = flight is not null && MathF.Abs( flight.Bearing ) > 0.005f ? flight.Bearing.ToString( "0.##" ) : "";
	}

	Widget Row( ArchStairPart stair, int index, float climb ) {
		var lane = stair.Lanes[index];
		var picked = index == canvas.Picked;

		var row = new Widget( this ) { Layout = Layout.Column() };

		row.Layout.Margin = 6;
		row.Layout.Spacing = 2;

		var head = row.Layout.AddRow();

		head.Spacing = 4;
		head.Add( new Label( lane.Climbs
			? $"Flight {ArchStairSteps.Ordinal( stair, lane )}"
			: $"Platform {ArchStairSteps.Ordinal( stair, lane )}" ) );
		head.AddStretchCell();
		head.Add( new Label( lane.Climbs ? $"{climb:0} climb" : lane.Rise > 0.05f ? $"+{lane.Rise:0}" : "level" ) );

		row.Layout.Add( new Label( $"{lane.Length:0} × {lane.Width:0}"
			+ (MathF.Abs( lane.Bearing ) > 0.05f ? $" · {lane.Bearing:0}°" : "")
			+ (lane.Guards.Count > 0 ? $" · {lane.Guards.Count} railing{(lane.Guards.Count == 1 ? "" : "s")}" : "") ) );

		row.MouseLeftPress += () => {
			canvas.Picked = index;
			canvas.Update();
			RebuildStack();
		};

		row.SetStyles( picked
			? "background-color: #3b6ea5; border-radius: 3px;"
			: "background-color: #2a2a2e; border-radius: 3px;" );

		return row;
	}

	void Rebuild() {
		if ( Subject() is null ) {
			Close();

			return;
		}

		Retitle();
		RebuildStack();

		canvas.Update();
	}

	void Retitle() {
		if ( Subject() is not { } stair ) {
			return;
		}

		var flights = stair.Lanes.Count( lane => lane.Climbs );
		var pads = stair.Lanes.Count - flights;
		var waiting = tool is { Unbuilt: true };

		caption.Text = $"{flights} flight{(flights == 1 ? "" : "s")}"
			+ (pads > 0 ? $" · {pads} platform{(pads == 1 ? "" : "s")}" : "")
			+ $" · {stair.TotalRise:0} climb"
			+ (waiting ? " · unbuilt" : "");

		build.Enabled = waiting;
	}

	void Build() {
		tool?.Build();
		Retitle();
	}

	// Repaints drawing only — rebuilding the step list per mouse-move kills drag perf
	void Preview() {
		Retitle();
		canvas.Update();
	}

	void Commit() {
		Write();
		Rebuild();
	}

	void Write() {
		if ( tool?.Plan is null ) {
			return;
		}

		Pierce();

		if ( live ) {
			tool.Commit( "Edit Stair" );
		} else {
			tool.Bank( "Edit Stair" );
		}
	}

	void Bend( float degrees ) {
		if ( Subject() is not { } stair || Held( stair ) is not { Climbs: true } lane ) {
			return;
		}

		ArchStairSteps.Bend( stair, lane, degrees );
		ArchStairLanes.Fit( stair.Core, stair.Lanes );

		Write();
		Preview();
	}

	void Pierce() {
		if ( Subject() is not { } stair || ArchStairSteps.Home( tool.Plan, stair ) is not { } room ) {
			return;
		}

		ArchPiercers.Pierce( tool.Plan, tool.Plan.OwnerOf( room ), room, stair, tool.Kit );
	}

	void AddFlight() {
		if ( Subject() is not { } stair ) {
			return;
		}

		var after = Held( stair ) ?? stair.Lanes.LastOrDefault();

		if ( ArchStairSteps.Add( tool.Plan, stair, after ) is not { } flight ) {
			return;
		}

		canvas.Picked = stair.Lanes.IndexOf( flight );
		Commit();
	}

	void SegmentFlight() {
		if ( Subject() is not { } stair || Held( stair ) is not { } lane ) {
			return;
		}

		if ( !ArchStairSteps.Segment( tool.Plan, stair, lane, 2 ) ) {
			Log.Info( "Architecture: only a flight with the run for two segments can be cut — lengthen it first." );

			return;
		}

		Commit();
	}

	void CurveFlight() {
		if ( Subject() is not { } stair || Held( stair ) is not { } lane ) {
			return;
		}

		if ( !ArchStairSteps.Curve( tool.Plan, stair, lane, segments, sweep ) ) {
			Log.Info( $"Architecture: a flight needs the run for {segments} segments to curve — lengthen it, or ask for fewer." );

			return;
		}

		Commit();
	}

	void Turn( StairTurn turn ) {
		if ( Subject() is not { } stair || Held( stair ) is not { } lane ) {
			return;
		}

		if ( !ArchStairSteps.Aim( stair, lane, turn ) ) {
			Log.Info( "Architecture: the first step aims the whole stair — turn one standing above it." );

			return;
		}

		Commit();
	}

	void AddPlatform() {
		if ( Subject() is not { } stair || Held( stair ) is not { } lane ) {
			return;
		}

		if ( !lane.Climbs ) {
			Log.Info( "Architecture: a platform is taken out of a FLIGHT — pick one and the space comes out of its run." );

			return;
		}

		if ( ArchStairSteps.AddLanding( tool.Plan, stair, lane ) is null ) {
			Log.Info( "Architecture: that flight has no run to spare — lengthen it first." );

			return;
		}

		canvas.Picked = stair.Lanes.IndexOf( lane ) + 1;
		Commit();
	}

	void AddRailing() {
		if ( Subject() is not { } stair || Held( stair ) is not { } lane ) {
			return;
		}

		lane.Guards.Add( new ArchStairGuardPart {
			Id = tool.Plan.AllocateId(),
			Edge = lane.Guarding( StairEdge.Left ) ? StairEdge.Right : StairEdge.Left
		} );

		canvas.PickedGuard = lane.Guards.Count - 1;
		Commit();
	}

	void DeleteStep() {
		if ( Subject() is not { } stair || Held( stair ) is not { } lane ) {
			return;
		}

		if ( !ArchStairSteps.Remove( stair, lane ) ) {
			Log.Info( "Architecture: a stair has to keep one step." );

			return;
		}

		canvas.Picked = Math.Min( canvas.Picked, stair.Lanes.Count - 1 );
		Commit();
	}

	ArchStairLane Held( ArchStairPart stair ) {
		return canvas.Picked >= 0 && canvas.Picked < stair.Lanes.Count ? stair.Lanes[canvas.Picked] : null;
	}

	protected override void OnKeyPress( KeyEvent e ) {
		if ( !e.HasCtrl || SceneEditorSession.Active is not { } session ) {
			base.OnKeyPress( e );

			return;
		}

		if ( e.Key == KeyCode.Z && !e.HasShift ) {
			session.UndoSystem.Undo();
		} else if ( e.Key == KeyCode.Y || (e.Key == KeyCode.Z && e.HasShift) ) {
			session.UndoSystem.Redo();
		} else {
			base.OnKeyPress( e );

			return;
		}

		e.Accepted = true;

		canvas.Picked = Math.Min( canvas.Picked, (Subject()?.Lanes.Count ?? 0) - 1 );

		Rebuild();
	}

	protected override void OnClosed() {
		base.OnClosed();

		if ( standing == this ) {
			standing = null;
		}
	}
}