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

namespace Sunless.Architecture;

[Title( "Fence" ), Icon( "fence" ), Group( "14" )]
public sealed class ArchFenceSubtool( ArchTool owner ) : ArchSubtool( owner ) {
	protected override ArchKind? DraftKind => ArchKind.Fence;

	public override ArchSurface[] Surfaces => new[]
	{
		ArchSurface.Railing, ArchSurface.Panel, ArchSurface.Post, ArchSurface.Wire, ArchSurface.Foundation
	};

	readonly List<ArchCurveNode> nodes = new();
	readonly ArchBarrierSpec spec = new();

	bool conform = true;

	protected override bool UsesDrag => false;

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

	protected override string Advice() => "Click to lay the line. Click the first node again to close the run.";

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

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

	protected override ArchRunState Run() => new( nodes.Count > 0, $"{nodes.Count} point{(nodes.Count == 1 ? "" : "s")}" );

	protected override void FinishRun() => Finish( false );

	protected override void CancelRun() => nodes.Clear();

	protected override void OnClick( Vector2 point ) {
		if ( nodes.Count >= 3 && (Flat( nodes[0] ) - point).Length < Owner.Kit.GridSize ) {
			Finish( true );
			return;
		}

		nodes.Add( ArchCurveNode.At( Grounded( point ) ) );
	}

	Vector3 Grounded( Vector2 point ) {
		if ( conform && ArchTerrain.Sample( Owner.Scene, point, out var sampled ) ) {
			return Owner.Grid.Draped( new Vector3( point.x, point.y, sampled + Owner.Kit.GroundClearance ) );
		}

		return Owner.Grid.Draped( new Vector3( point.x, point.y, Owner.LevelHeight ) );
	}

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

		if ( nodes.Count == 0 ) {
			ArchGhost.Note( new Vector3( point.x, point.y, Owner.LevelHeight + spec.Height ), "click to start the fence line" );
			return;
		}

		var line = new List<ArchCurveNode>( nodes ) { ArchCurveNode.At( Grounded( point ) ) };
		var curve = ArchCurve.Of( line );
		var barrier = ArchBarrierShape.Resolve( curve, spec, 0f, 0f, Owner.Kit );

		ArchGhost.Curve( curve );

		if ( spec.Style == BarrierStyle.Retaining ) {
			ArchGhost.Retaining( ArchRetainingShape.Resolve( barrier, Owner.Kit, new ArchGround( Owner.Scene ) ) );
		} else {
			ArchGhost.Barrier( barrier, spec );
		}

		Gizmo.Draw.Color = ArchGhost.Accent;
		Gizmo.Draw.LineSphere( nodes[0].Position, Owner.Kit.GridSize * 0.5f );
	}

	void Finish( bool closed ) {
		var building = Owner.ActiveBuilding();

		if ( building is not null && nodes.Count >= 2 ) {
			var fence = new ArchFencePart {
				Id = Owner.Plan.AllocateId(),
				Name = $"Fence{building.Fences.Count + 1}",
				Nodes = nodes.Select( node => node.Copy() ).ToList(),
				Closed = closed,
				Barrier = spec.Copy()
			};

			building.Fences.Add( fence );
			FinishDraft( fence.Id );
			Owner.Commit( "Create Fence" );
		}

		nodes.Clear();
	}

	static Vector2 Flat( ArchCurveNode node ) => new( node.Position.x, node.Position.y );

	// One list: how the line is laid, then the barrier's own rows contributed straight into this sequence rather
	// than opened as a second one - the style decides which of them exist, and that branch lives with the barrier.
	protected override void BuildOptions( ToolSidebarWidget panel ) {
		using var flow = ArchWorkflow.In( panel, Scope( "flow" ), Refresh );

		flow.Step( "Line", conform ? "Draped" : "Flat", conform ? "landscape" : "horizontal_rule", PickLine );

		ArchBarrierUi.Steps( flow, spec, Refresh, null );
	}

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

		grid.Pick( "Draped — every node drops onto the ground under it, which is what a boundary on a slope needs",
			"line_draped", "landscape", conform, () => { conform = true; step.Chose(); } );

		grid.Pick( "Flat — the run lies on this storey however the ground falls", "line_flat", "horizontal_rule",
			!conform, () => { conform = false; step.Chose(); } );
	}
}