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

namespace Sunless.Architecture;

public enum ArchViewAxis {
	Free,
	Top,
	Front,
	Side
}

public abstract partial class ArchTool {
	public float? SectionDepth { get; set; }

	// Read off the viewport, never stored - CTRL+SPACE and the gizmo change the view without passing through here.
	public ArchViewAxis Axis => AxisOf( FocusedViewport() );

	public bool InPlanMode => Axis != ArchViewAxis.Free;

	public bool InElevation => Axis is ArchViewAxis.Front or ArchViewAxis.Side;

	public static SceneViewportWidget FocusedViewport() => SceneViewWidget.Current?.LastSelectedViewportWidget;

	// Ordered by pane id - a dictionary hands them back in whatever order it likes.
	public static List<SceneViewportWidget> Panes() {
		if ( SceneViewWidget.Current is not { } view ) {
			return new List<SceneViewportWidget>();
		}

		return view._viewports
			.OrderBy( entry => entry.Key )
			.Select( entry => entry.Value )
			.Where( pane => pane.IsValid() )
			.ToList();
	}

	public static ArchViewAxis AxisOf( SceneViewportWidget viewport ) {
		return viewport?.State?.View switch {
			SceneViewportWidget.ViewMode.Top2d => ArchViewAxis.Top,
			SceneViewportWidget.ViewMode.Front2d => ArchViewAxis.Front,
			SceneViewportWidget.ViewMode.Side2d => ArchViewAxis.Side,
			_ => ArchViewAxis.Free
		};
	}

	static SceneViewportWidget.ViewMode ModeOf( ArchViewAxis axis ) {
		return axis switch {
			ArchViewAxis.Top => SceneViewportWidget.ViewMode.Top2d,
			ArchViewAxis.Front => SceneViewportWidget.ViewMode.Front2d,
			ArchViewAxis.Side => SceneViewportWidget.ViewMode.Side2d,
			_ => SceneViewportWidget.ViewMode.Perspective
		};
	}

	// The viewport still owns which view it is in, so the engine's own controls stay first-class.
	public void Show( SceneViewportWidget viewport, ArchViewAxis axis ) {
		if ( viewport is null ) {
			return;
		}

		// Setting the mode picks the rotation, so framing follows.
		viewport.State.View = ModeOf( axis );

		if ( axis != ArchViewAxis.Free ) {
			FrameOn( viewport, EditedExtent() );
		}

		viewport.SaveState();
	}

	// SetLayout destroys every viewport widget, so the split has to come before the assignments.
	public void Arrange( SceneViewWidget.ViewportLayoutMode layout, params ArchViewAxis[] axes ) {
		if ( SceneViewWidget.Current is not { } view ) {
			return;
		}

		view.SetLayout( layout );

		var panes = Panes();

		for ( var index = 0; index < panes.Count && index < axes.Length; index++ ) {
			Show( panes[index], axes[index] );
		}

		view.SaveState();
	}

	public void Frame() => Frame( EditedExtent() );

	public void Frame( BBox bounds ) {
		foreach ( var pane in Panes() ) {
			if ( AxisOf( pane ) != ArchViewAxis.Free ) {
				FrameOn( pane, bounds );
			}
		}
	}

	void FrameOn( SceneViewportWidget viewport, BBox bounds ) {
		var size = viewport.Renderer?.Size ?? new Vector2( 1280f, 800f );
		var aspect = size.y > 1f ? size.x / size.y : 1.6f;

		viewport.State.CameraPosition = bounds.Center;
		viewport.State.CameraOrthoHeight = ArchShot.Framing( bounds, viewport.State.CameraRotation, aspect, 0.12f );
	}

	BBox EditedExtent() => Extent( EditedBuilding() );

	public float Depth( ArchViewAxis axis ) {
		if ( SectionDepth is { } chosen ) {
			return Grid.Height( chosen );
		}

		var centre = EditedExtent().Center;

		return Grid.Height( axis == ArchViewAxis.Front ? centre.x : centre.y );
	}

}