Editor/UI/ArchGuideWindow.cs
using System;
using System.Linq;
using Editor;

namespace Sunless.Architecture;

// Where a tool's how-to is read. Modeless and re-pointed rather than one window per tool, so pressing HOW TO on
// a second tool moves the one window rather than stacking another over the scene view.
public sealed class ArchGuideWindow : Window {
	const int Reading = 560;
	const int Gutter = 28;

	static ArchGuideWindow standing;

	public static void Open( ArchGuide guide ) {
		if ( guide is null ) {
			return;
		}

		if ( standing.IsValid() ) {
			standing.Read( guide );
			standing.Focus();

			return;
		}

		standing = new ArchGuideWindow( guide );
		standing.Show();
	}

	Layout page;

	ArchGuideWindow( ArchGuide guide ) {
		DeleteOnClose = true;
		Size = new Vector2( Reading + Gutter * 2 + 24, 720 );
		MinimumSize = new Vector2( 420, 360 );
		SetWindowIcon( "menu_book" );

		var scroll = new ScrollArea( this );

		scroll.Canvas = new Widget( scroll ) { Layout = Layout.Column() };
		scroll.Canvas.Layout.Margin = Gutter;
		scroll.Canvas.Layout.Spacing = 0;

		page = scroll.Canvas.Layout;

		Canvas = new Widget( this ) { Layout = Layout.Column() };
		Canvas.Layout.Add( scroll, 1 );

		Read( guide );
	}

	void Read( ArchGuide guide ) {
		WindowTitle = $"How to — {guide.Title}";

		page.Clear( true );
		page.Add( new ArchGuideHeading( guide.Title, 15f, 700, 34f ) );
		page.Add( Paragraph( guide.Lead, 0.82f, 11f ) );

		foreach ( var chapter in guide.Chapters ) {
			Chapter( chapter );
		}

		page.AddStretchCell();
	}

	void Chapter( ArchGuideChapter chapter ) {
		page.Add( new ArchGuideRule() );
		page.Add( new ArchGuideHeading( chapter.Heading, 11f, 700, 30f ) );

		if ( ArchIcons.Picture( chapter.Picture ) is { } drawn ) {
			page.Add( new ArchGuidePicture( drawn, Reading ) );
		}

		if ( chapter.Plates.Length > 0 ) {
			Plates( chapter );
		}

		foreach ( var line in chapter.Body ) {
			page.Add( Paragraph( line, 0.72f, 10.5f ) );
		}

		for ( var index = 0; index < chapter.Steps.Length; index++ ) {
			page.Add( new ArchGuideStep( index + 1, chapter.Steps[index], Reading ) );
		}
	}

	void Plates( ArchGuideChapter chapter ) {
		var row = page.AddRow();

		row.Spacing = 8;
		row.Margin = new Sandbox.UI.Margin( 0, 4, 0, 8 );

		foreach ( var plate in chapter.Plates ) {
			row.Add( new ArchGuidePlateView( plate ) );
		}

		row.AddStretchCell();
	}

	static Widget Paragraph( string text, float alpha, float size ) {
		var label = new Label( text ) { WordWrap = true, FixedWidth = Reading };

		label.MinimumHeight = MathF.Ceiling( text.Length / 82f ) * 17f + 10f;
		label.SetStyles( $"font-size: {size}px; color: rgba(255,255,255,{alpha}); padding-bottom: 6px;" );

		return label;
	}
}

sealed class ArchGuideHeading : Widget {
	readonly string text;
	readonly float size;
	readonly int weight;

	public ArchGuideHeading( string text, float size, int weight, float height ) {
		this.text = text;
		this.size = size;
		this.weight = weight;

		FixedHeight = height;
	}

	protected override void OnPaint() {
		Paint.TextAntialiasing = true;
		Paint.SetDefaultFont( size, weight );
		Paint.SetPen( Theme.Text );
		Paint.DrawText( LocalRect, text, TextFlag.LeftBottom );
	}
}

sealed class ArchGuideRule : Widget {
	public ArchGuideRule() {
		FixedHeight = 14f;
	}

	protected override void OnPaint() {
		Paint.SetPen( Theme.Text.WithAlpha( 0.1f ) );
		Paint.DrawLine( new Vector2( 0f, 8f ), new Vector2( Width, 8f ) );
	}
}

// Drawn at the reading width and letterboxed to the file's own aspect, so a plate drawn at any size lands
// looking deliberate rather than stretched.
sealed class ArchGuidePicture : Widget {
	readonly string path;

	public ArchGuidePicture( string path, float width ) {
		this.path = path;

		var image = Pixmap.FromFile( path );
		var aspect = image is not null && image.Width > 0 ? (float)image.Height / image.Width : 0.5f;

		FixedWidth = width;
		FixedHeight = MathF.Round( width * aspect ) + 10f;
	}

	protected override void OnPaint() {
		Paint.Draw( new Rect( 0f, 0f, Width, Height - 10f ), path, 1f, 4f );
	}
}

// The tool's OWN icon art, captioned. A chapter can show the very tiles it is talking about without anyone
// drawing anything new, and a slug with no PNG behind it falls back to its glyph exactly as the tile does.
sealed class ArchGuidePlateView : Widget {
	readonly string icon;
	readonly string caption;

	public ArchGuidePlateView( ArchGuidePlate plate ) {
		icon = ArchIcons.Get( plate.Slug, plate.Glyph );
		caption = plate.Caption;

		FixedWidth = 92f;
		FixedHeight = 86f;
	}

	protected override void OnPaint() {
		Paint.Antialiasing = true;
		Paint.TextAntialiasing = true;

		Paint.SetBrushAndPen( Theme.Text.WithAlpha( 0.04f ), Theme.Text.WithAlpha( 0.1f ) );
		Paint.DrawRect( LocalRect.Shrink( 0f, 0f, 1f, 1f ), 4 );

		Paint.SetPen( Theme.TextControl.WithAlpha( 0.9f ) );
		Paint.DrawIcon( new Rect( 0f, 8f, Width, 46f ), icon, 40f, TextFlag.Center );

		Paint.SetDefaultFont( 7.5f, 600 );
		Paint.SetPen( Theme.Text.WithAlpha( 0.65f ) );
		Paint.DrawText( new Rect( 4f, 56f, Width - 8f, 20f ), caption, TextFlag.Center );
	}
}

sealed class ArchGuideStep : Widget {
	readonly int number;
	readonly string text;

	public ArchGuideStep( int number, string text, float width ) {
		this.number = number;
		this.text = text;

		FixedWidth = width;
		FixedHeight = MathF.Ceiling( text.Length / 74f ) * 17f + 12f;
	}

	protected override void OnPaint() {
		Paint.Antialiasing = true;
		Paint.TextAntialiasing = true;

		var badge = new Rect( 0f, 3f, 16f, 16f );

		Paint.SetBrushAndPen( Theme.Blue.WithAlpha( 0.85f ) );
		Paint.DrawRect( badge, 3 );

		Paint.SetPen( Theme.TabBackground );
		Paint.SetDefaultFont( 6.5f, 700 );
		Paint.DrawText( badge, number.ToString(), TextFlag.Center );

		Paint.SetDefaultFont( 10.5f, 400 );
		Paint.SetPen( Theme.Text.WithAlpha( 0.8f ) );
		Paint.DrawText( new Rect( 26f, 0f, Width - 26f, Height ), text, TextFlag.LeftTop | TextFlag.WordWrap );
	}
}