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

namespace Sunless.Architecture;

[Title( "Cabinets" ), Icon( "kitchen" ), Group( "16" )]
public sealed class ArchCabinetSubtool( ArchTool owner ) : ArchSubtool( owner ) {
	protected override ArchKind? DraftKind => ArchKind.Cabinet;

	public override ArchSurface[] Surfaces => new[]
	{
		ArchSurface.CabinetCarcass, ArchSurface.CabinetFront, ArchSurface.Worktop
	};

	readonly ArchCabinetPart draft = new();

	ArchCabinetType drafted;

	ArchCabinetPart adopted;

	CabinetStanding stands;

	int quarters;

	ArchCabinetPart Edited => Lent ? adopted : Owner.Picked?.Item as ArchCabinetPart;

	ArchCabinetPart Editing => Edited ?? draft;

	protected override bool UsesDrag => true;

	protected override bool Adjusts => true;

	public override bool Adopts => true;

	// Backed runs use wide snap band; free runs use tight to avoid spurious wall snap
	public override ArchSnapReach SnapReach( float authored ) {
		return stands == CabinetStanding.Backed ? ArchWallSnap.Flush( authored ) : base.SnapReach( authored );
	}

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

	protected override string Advice() => "Drag along a wall, or stand a run free anywhere on the floor — R turns it a quarter.";

	public override void OnEnabled() {
		base.OnEnabled();

		ArchWorkflow.Restart( Scope( "flow" ) );
	}

	protected override void Adopt( ArchSelection picked ) {
		adopted = picked?.Item as ArchCabinetPart;
	}

	public override bool Turns() {
		if ( stands == CabinetStanding.Free && Placing ) {
			quarters = (quarters + 1) % 4;

			return true;
		}

		if ( Edited is not { } run ) {
			return false;
		}

		run.Outward = ArchCabinets.Turned( run.Facing, 1 );

		Owner.Commit( $"Turn {run.Name}" );

		return true;
	}

	ArchCabinetType Type() {
		return drafted ??= ArchCabinetTypes.Copy( ArchCabinetTypes.Find( Owner.Kit, Editing.Type ) );
	}

	protected override void DrawHover( Vector2 point ) {
		base.DrawHover( point );

		Sketch( point, point );
	}

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

		Sketch( DragStart, DragCurrent );
	}

	CabinetTier Aimed() {
		if ( draft.Tier != CabinetTier.Auto ) {
			return draft.Tier;
		}

		return Pointer.Face == ArchCursorFace.Ceiling || Pointer.SnapAbove ? CabinetTier.Hanging : CabinetTier.Auto;
	}

	float Module() {
		return Editing.ModuleWidth > 1f ? Editing.ModuleWidth : Type().Width;
	}

	ArchFixtureStation? Station( Vector2 from, Vector2 to ) {
		if ( stands == CabinetStanding.Free ) {
			return null;
		}

		return ArchFixtureAnchor.Station( Owner.Plan.Buildings, Owner.Level, Owner.Grid, from, to,
			Module(), Owner.Plan, false, ArchWallSnap.Flush( Module() ).Face );
	}

	void Sketch( Vector2 from, Vector2 to ) {
		if ( Resolved( from, to, out var room ) is not { } sketch ) {
			return;
		}

		var shown = Continued( sketch, room ) is { } standing ? ArchCabinets.Joined( standing, sketch ) : sketch;

		ArchCabinets.Preview( Owner.Plan, Owner.Kit, shown, room );
	}

	ArchCabinetPart Continued( ArchCabinetPart placed, ArchRoom room ) {
		return ArchCabinets.Continues( Owner.Plan, room, placed, Module() );
	}

	ArchCabinetPart Resolved( Vector2 from, Vector2 to, out ArchRoom room ) {
		Seed();

		room = null;

		if ( Station( from, to ) is { } station
			&& ArchCabinets.Place( Owner.Plan, Owner.Kit, station, draft, out room ) is { } against ) {
			return against;
		}

		room = Owner.Contained( from );

		return room is not null && room.Floor == Owner.Level
			? ArchCabinets.Free( Owner.Plan, room, Owner.Grid, from, to, quarters, Module(), draft )
			: null;
	}

	void Seed() {
		draft.Type = Type().Name;
		draft.Tier = Aimed();
	}

	protected override void OnDrag( Vector2 from, Vector2 to ) {
		if ( Resolved( from, to, out var room ) is not { } placed ) {
			Log.Info( $"Architecture: nothing on level {Owner.Level} to stand cabinets on — draw a room first,"
				+ " then drag inside it, along a wall or across its floor." );

			return;
		}

		if ( Continued( placed, room ) is { } standing ) {
			ArchCabinets.Absorb( standing, placed );

			Owner.Picked = new ArchSelection { Item = standing, Room = room, Building = Owner.Plan.OwnerOf( room ) };

			Drew( standing );
			FinishDraft( standing.Id );
			Owner.Commit( $"Extend {standing.Name}" );

			return;
		}

		if ( ArchCabinets.Shaped( Owner.Plan, Owner.Kit, placed, room ) is null ) {
			return;
		}

		room.Cabinets.Add( placed );

		Owner.Picked = new ArchSelection { Item = placed, Room = room, Building = Owner.Plan.OwnerOf( room ) };

		Drew( placed );
		FinishDraft( placed.Id );
		Owner.Commit( "Place Cabinets" );
	}

	protected override void BuildOptions( ToolSidebarWidget panel ) {
		Inherit();

		var type = Type();

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

		flow.Step( "Standing", StandingName, StandingGlyph, PickStanding );

		flow.Step( "Type", ArchCabinetUi.Titled( type ), ArchCabinetStage.Glyph( type ), step => PickType( panel, step ) );

		flow.Step( "Tier", TierName, ArchCabinetUi.Glyph( Aimed() ), PickTier );

		if ( type.Prefabbed ) {
			flow.Step( "Finish", FinishName, "palette", BuildFinish );
			flow.Step( "Sizes", SizesName, "straighten", BuildSizes );

			return;
		}

		flow.Step( "Front", FrontName, "border_all", PickFront );

		flow.Step( "Inside", InsideName, "inventory_2", BuildInside );

		flow.Step( "Sizes", SizesName, "straighten", BuildSizes );

		flow.Step( "Worktop", WorktopName, "countertops", BuildWorktop );
	}

	string FinishName {
		get {
			var group = string.IsNullOrWhiteSpace( Editing.MaterialGroup ) ? Type().MaterialGroup : Editing.MaterialGroup;

			return string.IsNullOrWhiteSpace( group ) ? "As modelled" : group;
		}
	}

	void BuildFinish( ArchWorkflowStep step ) {
		ArchCabinetUi.Finish( step.Layout, Editing, Type(), Restage, step.Chose );
	}

	void Inherit() {
		if ( Edited is { } run && !string.Equals( run.Type, Type().Name, StringComparison.OrdinalIgnoreCase ) ) {
			drafted = ArchCabinetTypes.Copy( ArchCabinetTypes.Find( Owner.Kit, run.Type ) );
		}
	}

	string StandingName {
		get {
			if ( stands == CabinetStanding.Backed ) {
				return "Against a wall";
			}

			return quarters == 0 ? "Free, facing in" : $"Free, turned {quarters * 90}";
		}
	}

	string StandingGlyph => stands == CabinetStanding.Backed ? "align_horizontal_left" : "rotate_90_degrees_cw";

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

		grid.Pick( "Against a wall — lies flush on whatever elevation the drag lands on, snapping to corners and jambs",
			"cabinet_backed", "align_horizontal_left", stands == CabinetStanding.Backed,
			() => Stand( CabinetStanding.Backed, step ) );

		grid.Pick( "Free-standing — anywhere on the floor, facing into the room; R turns it a quarter",
			"cabinet_free", "rotate_90_degrees_cw", stands == CabinetStanding.Free,
			() => Stand( CabinetStanding.Free, step ) );
	}

	void Stand( CabinetStanding standing, ArchWorkflowStep step ) {
		stands = standing;

		step.Chose();
	}

	void PickType( ToolSidebarWidget panel, ArchWorkflowStep step ) {
		var hidden = ArchStorage.HiddenCabinetTypes();

		var shelf = new ArchPresetBrowser<ArchCabinetType>( panel, Owner.Kit, Scope( "types" ), "Cabinet types" ) {
			Chosen = type => string.Equals( Type().Name, type.Name, StringComparison.OrdinalIgnoreCase ),
			Choose = type => Adopt( type, step ),
			Design = () => new ArchCabinetDesigner( panel, Owner, Type(), designed => Designed( designed, step ) ).Show(),
			Reload = Refresh,
			Exports = "cabinet",
			Removes = item => Ships( item.Value ) ? $"Hide {item.Name}" : $"Remove {item.Name}",
			Remove = item => Remove( item.Value ),
			Restore = () => {
				ArchStorage.RestoreCabinetTypes();
				Reread();
			}
		};

		shelf.DesignButton.Visible = true;
		shelf.DesignButton.ToolTip = "Design a cabinet type";
		shelf.ReloadButton.Visible = true;
		shelf.RestoreButton.Visible = hidden.Count > 0;
		shelf.RestoreButton.ToolTip = $"Bring back {string.Join( ", ", hidden )}";
		shelf.Set( Offered() );

		step.Add( shelf );
	}

	static bool Ships( ArchCabinetType type ) {
		return ArchCabinetTypes.Shipped()
			.Any( shipped => string.Equals( shipped.Name, type.Name, StringComparison.OrdinalIgnoreCase ) );
	}

	void Remove( ArchCabinetType type ) {
		if ( !ArchStorage.RemoveCabinetType( type.Name ) ) {
			Log.Warning( $"Architecture: could not remove the cabinet type '{type.Name}'." );

			return;
		}

		Reread();
	}

	void Reread() {
		if ( Owner.Kit?.Cabinets is { } offered ) {
			offered.Clear();
			ArchCatalogs.Load().Stock( ArchShelf.Cabinets, offered );
			offered.RemoveAll( type => type.Hidden );
		}

		if ( !Kinds().Any( type => string.Equals( type.Name, draft.Type, StringComparison.OrdinalIgnoreCase ) ) ) {
			drafted = null;
			draft.Type = Kinds().FirstOrDefault()?.Name ?? "";
		}

		Refresh();
	}

	List<ArchPresetItem<ArchCabinetType>> Offered() {
		return Kinds()
			.Select( type => new ArchPresetItem<ArchCabinetType> {
				Value = type,
				Name = ArchCabinetUi.Titled( type ),
				Detail = ArchCabinetStage.Describe( type ),
				Identity = ArchCabinetStage.Identity( type ),
				Category = ArchCabinetStage.Category( type ),
				Glyph = ArchCabinetStage.Glyph( type ),
				Tags = $"{type.Name} {type.Detail}",
				View = ArchPresetPreview.Quarter,
				Focus = ArchPreviewFocus.Whole,
				Recipe = () => ArchCabinetStage.Of( type )
			} )
			.ToList();
	}

	List<ArchCabinetType> Kinds() {
		var offered = Owner.Kit?.Cabinets;

		return offered is { Count: > 0 } ? offered : ArchCabinetTypes.Shipped();
	}

	void Adopt( ArchCabinetType type, ArchWorkflowStep step ) {
		Answer( () => {
			drafted = ArchCabinetTypes.Copy( type );
			Editing.Type = type.Name;
		}, step );
	}

	void Designed( ArchCabinetType designed, ArchWorkflowStep step ) {
		Owner.RegisterCabinetType( designed );
		Adopt( designed, step );
	}

	string TierName => Aimed() switch {
		CabinetTier.Base => "Base",
		CabinetTier.Hanging => "Hanging",
		CabinetTier.Tall => "Tall",
		_ => $"Following {Type().Tier}"
	};

	void PickTier( ArchWorkflowStep step ) {
		ArchCabinetUi.Tier( step.Layout, Editing.Tier, tier => Answer( () => Editing.Tier = tier, step ) );
	}

	string FrontName => ArchCabinetUi.Named( Editing.On( ArchCabinetMounts.Front ) ?? Type().On( ArchCabinetMounts.Front ) );

	void PickFront( ArchWorkflowStep step ) {
		ArchCabinetUi.Front( step.Layout, Editing, Type(), ArchCabinetMounts.Front, step.Chose, Restage );
	}

	string InsideName {
		get {
			var type = Type();
			var part = Editing;

			return ArchPartUi.Listed( (part.Interior || type.Interior, "interior"),
				(part.Handles ?? type.Handles, "handles"),
				((part.DrawerRows ?? type.DrawerRows) > 0, "drawers") );
		}
	}

	void BuildInside( ArchWorkflowStep step ) {
		ArchCabinetUi.Inside( step.Layout, Editing, Type(), Restage, Refresh );
	}

	string SizesName {
		get {
			var type = Type();
			var module = Editing.ModuleWidth > 1f ? Editing.ModuleWidth : type.Width;
			var depth = Editing.Depth > 1f ? Editing.Depth : type.Depth;

			return $"{module:0} × {depth:0}";
		}
	}

	void BuildSizes( ArchWorkflowStep step ) {
		ArchCabinetUi.Sizes( step.Layout, Editing, Type(), Restage, Settled(), Refresh );
	}

	string WorktopName => $"{Editing.TopThickness ?? Type().TopThickness:0.##} thick";

	void BuildWorktop( ArchWorkflowStep step ) {
		ArchCabinetUi.Worktop( step.Layout, Editing, Type(), Restage, Settled(), Refresh );
	}

	ArchCabinetShape Settled() {
		return Edited is { } run
			? ArchCabinetShape.Resolve( Owner.Plan, Owner.Kit, Owner.Plan.HostOf<ArchRoom>( run ), null, run )
			: null;
	}

	void Answer( Action change, ArchWorkflowStep step ) {
		change();

		Restage();

		step.Chose();
	}

	void Restage() {
		if ( Edited is not null ) {
			Owner.Touch();
		}
	}
}