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

namespace Sunless.Architecture;

public partial class ArchTool {
	void DrawPlan() {
		using ( Gizmo.Scope( "arch-plan" ) ) {
			Gizmo.Draw.IgnoreDepth = true;
			Gizmo.Draw.LineThickness = 2f;

			DrawDebugFaces();

			if ( InElevation ) {
				DrawSection();

				return;
			}

			if ( EditedBuilding() is { } building && !LayerHidden( building.Id ) ) {
				DrawNeighbours( building );

				if ( Placing ) {
					DrawWorkPlane( building );
				}

				DrawStoreyBelow( building );
				DrawBuilding( building );
				Identify( building );
			}

			foreach ( var road in Plan.Roads() ) {
				if ( LayerHidden( road.Id ) ) {
					continue;
				}

				DrawRoad( road );
			}

			DrawUnits();
			DrawPicked();
		}
	}

	void DrawPicked() {
		if ( Picked?.Item is not { } item || ArchKinds.Load().Claiming( item ) is not { } manifest ) {
			return;
		}

		var id = ArchPlanStore.IdOf( item );

		if ( id > 0 && LayerHidden( id ) ) {
			return;
		}

		var band = ArchExtents.Load()
			.Measure( manifest.Kind, item, new ArchExtentContext( Plan, Kit, Picked.Room, Picked.Building, this ) );

		using ( Gizmo.Scope( "arch-picked" ) ) {
			Gizmo.Draw.IgnoreDepth = true;
			Gizmo.Draw.LineThickness = 3f;
			Gizmo.Draw.Color = ArchGhost.Picked;

			if ( band.Any ) {
				Gizmo.Draw.LineBBox( band.Box );

				return;
			}

			var lift = Picked.Lift( Plan, Kit );
			var foot = manifest.Height( item, Picked.Room?.BaseHeight ?? 0f ) + lift;

			foreach ( var loop in manifest.Loops( item ) ) {
				ArchGhost.Prism( loop, foot, foot + PickedPost );
			}
		}
	}

	const float PickedPost = 12f;

	void DrawUnits() {
		var previews = ArchPreviews.Load();

		foreach ( var unit in Plan.Units ) {
			if ( unit is ArchBuilding or ArchRoadPart || LayerHidden( unit.Id ) ) {
				continue;
			}

			previews.Draw( unit.Kind,
				new ArchPreviewContext( unit, null, null, 0f, Picked?.Targets( unit ) == true, this ) );
		}
	}

	public ArchBuilding EditedBuilding() {
		return Picked?.Building ?? ActiveBuilding();
	}

	void Identify( ArchBuilding building ) {
		var frame = Frame( building.Rooms );

		if ( !Bounds( building, frame, out var min, out var max ) ) {
			return;
		}

		var height = LevelHeight + ArchAsks.Lift( Plan, building, Kit );
		var box = ArchFootprint.Turned( ArchFootprint.Rect( min, max ), frame.Pivot, frame.Degrees );

		Gizmo.Draw.LineThickness = 3f;
		Gizmo.Draw.Color = ArchGhost.Accent;

		ArchGhost.Brackets( box, height );

		Gizmo.Draw.LineThickness = 2f;

		ArchGhost.Tag( new Vector3( box[3].x, box[3].y, height ), $"{building.Name} · L{Level}", ArchGhost.Accent );
	}

	(Vector2 Pivot, float Degrees) Frame( IEnumerable<ArchRoom> rooms ) {
		var loops = rooms.Select( Outline ).Where( loop => loop.Count >= 3 ).ToList();

		ArchFootprint.Bearing( loops, out var degrees );

		return (ArchFootprint.Pivot( loops ), degrees);
	}

	void DrawNeighbours( ArchBuilding edited ) {
		if ( Plan.Buildings.Count < 2 ) {
			return;
		}

		var found = AimPoint( out var point );

		Gizmo.Draw.LineThickness = 1f;

		foreach ( var building in Plan.Buildings ) {
			var frame = Frame( building.Rooms );

			if ( building == edited || LayerHidden( building.Id ) || !Bounds( building, frame, out var min, out var max ) ) {
				continue;
			}

			var at = ArchFootprint.Turned( new Vector2( point.x, point.y ), frame.Pivot, -frame.Degrees );
			var over = found && at.x >= min.x && at.y >= min.y && at.x <= max.x && at.y <= max.y;
			var box = ArchFootprint.Turned( ArchFootprint.Rect( min, max ), frame.Pivot, frame.Degrees );

			Gizmo.Draw.Color = ArchGhost.Line.WithAlpha( over ? 0.55f : 0.12f );

			DrawLoop( box, LevelHeight );

			if ( over ) {
				ArchGhost.Tag( new Vector3( box[3].x, box[3].y, LevelHeight ), building.Name, ArchGhost.Line );
			}
		}

		Gizmo.Draw.LineThickness = 2f;
	}

	bool Bounds( ArchBuilding building, (Vector2 Pivot, float Degrees) frame, out Vector2 min, out Vector2 max ) {
		var low = new Vector2( float.MaxValue, float.MaxValue );
		var high = new Vector2( float.MinValue, float.MinValue );
		var found = false;

		foreach ( var room in building.Rooms ) {
			foreach ( var corner in Outline( room ).Select( point => ArchFootprint.Turned( point, frame.Pivot, -frame.Degrees ) ) ) {
				low = Vector2.Min( low, corner );
				high = Vector2.Max( high, corner );
				found = true;
			}
		}

		min = low;
		max = high;

		return found && high.x - low.x > 1f && high.y - low.y > 1f;
	}

	void DrawStoreyBelow( ArchBuilding building ) {
		if ( Level <= 0 ) {
			return;
		}

		var height = LevelHeight + ArchAsks.Lift( Plan, building, Kit );

		Gizmo.Draw.LineThickness = 3f;
		Gizmo.Draw.Color = ArchGhost.Accent.WithAlpha( 0.45f );

		foreach ( var room in building.Rooms ) {
			if ( room.Floor != Level - 1 || LayerHidden( room.Id ) ) {
				continue;
			}

			var loop = Outline( room );

			if ( loop.Count >= 3 ) {
				ArchGhost.Ring( loop, height );
			}
		}

		Gizmo.Draw.LineThickness = 2f;
	}

	void DrawBuilding( ArchBuilding building ) {
		var inside = HoveredRoom( building );
		var lift = ArchAsks.Lift( Plan, building, Kit );
		var previews = ArchPreviews.Load();

		foreach ( var room in building.Rooms ) {
			DrawRoom( room, inside, lift, building, previews );
		}

		foreach ( var cutout in building.Cutouts ) {
			var sourceId = cutout.OwnerId != 0 ? cutout.OwnerId : cutout.Id;

			if ( !Shown( cutout.Level, sourceId ) ) {
				continue;
			}

			Gizmo.Draw.Color = Color.Red.WithAlpha( 0.8f );

			ArchGhost.Ring( cutout.Outline(), FloorOf( cutout.Level ) + lift );
		}

		foreach ( var cut in building.Cuts ) {
			if ( Shown( cut.Level, cut.Id ) ) {
				DrawCut( cut, lift );
			}
		}

		foreach ( var roof in building.Roofs ) {
			if ( !Shown( roof.Level, roof.Id ) ) {
				continue;
			}

			var selected = Picked?.Targets( roof ) == true;

			Gizmo.Draw.Color = (selected ? Color.Yellow : Color.Orange).WithAlpha( 0.6f );
			DrawLoop( roof.Outline(), roof.BaseHeight + lift );
		}

		foreach ( var fence in building.Fences ) {
			if ( LayerHidden( fence.Id ) ) {
				continue;
			}

			var picked = Picked?.Targets( fence ) == true;

			Gizmo.Draw.Color = picked ? Color.Yellow : ArchGhost.Line.WithAlpha( 0.8f );
			ArchGhost.Curve( Curve( fence ) );

			if ( !picked ) {
				continue;
			}

			if ( fence.Barrier.Style == BarrierStyle.Retaining ) {
				ArchGhost.Retaining( Retaining( fence ) );

				continue;
			}

			ArchGhost.Barrier( Barrier( fence ), fence.Barrier );
		}

		foreach ( var pipe in building.Downpipes ) {
			if ( LayerHidden( pipe.Id ) ) {
				continue;
			}

			var picked = Picked?.Targets( pipe ) == true;

			Gizmo.Draw.Color = picked ? Color.Yellow : Color.Cyan.WithAlpha( 0.7f );

			Gizmo.Draw.Line(
				new Vector3( pipe.Outlet.x, pipe.Outlet.y, pipe.TopHeight + lift ),
				new Vector3( pipe.Wall.x, pipe.Wall.y, pipe.BaseHeight + lift ) );

			if ( picked ) {
				Gizmo.Draw.LineSphere( new Vector3( pipe.Outlet.x, pipe.Outlet.y, pipe.TopHeight + lift ), 6f );
			}
		}

		foreach ( var run in building.Pipes ) {
			DrawPipeRun( run, building, lift, previews );
		}

		foreach ( var bracket in building.Brackets ) {
			DrawBrackets( bracket, building, lift, previews );
		}
	}

	void DrawPipeRun( ArchPipePart run, ArchBuilding building, float lift, ArchPreviews previews ) {
		if ( LayerHidden( run.Id ) ) {
			return;
		}

		previews.Draw( ArchKind.Pipe,
			new ArchPreviewContext( run, null, building, lift, Picked?.Targets( run ) == true, this ) );
	}

	void DrawBrackets( ArchPipeBracketPart bracket, ArchBuilding building, float lift, ArchPreviews previews ) {
		if ( LayerHidden( bracket.Id ) ) {
			return;
		}

		previews.Draw( ArchKind.Bracket,
			new ArchPreviewContext( bracket, null, building, lift, Picked?.Targets( bracket ) == true, this ) );
	}

	void DrawCut( ArchCutPart cut, float lift ) {
		var picked = Picked?.Targets( cut ) == true;

		Gizmo.Draw.Color = (picked ? Color.Yellow : Color.Red).WithAlpha( 0.7f );

		foreach ( var segment in cut.Segments.Where( leg => leg.HasLoop || leg.Length > 1f ) ) {
			var loop = segment.Outline();
			var band = ArchCut.WorldBand( segment, lift );

			ArchGhost.Prism( loop, band.x, band.y );

			if ( !picked ) {
				continue;
			}

			ArchFootprint.Bounds( loop, out var low, out var high );

			ArchGhost.Brackets( loop, band.y );
			ArchGhost.Note( new Vector3( high.x, high.y, band.y ),
				$"{cut.Name} — {high.x - low.x:0} x {high.y - low.y:0}, {segment.TopHeight - segment.BaseHeight:0} deep" );
		}
	}

	ArchRoom HoveredRoom( ArchBuilding building ) {
		return AimPoint( out var point ) ? Contained( building, point ) : null;
	}

	void DrawRoad( ArchRoadPart road ) {
		foreach ( var cut in road.Cuts ) {
			if ( !LayerHidden( cut.Id ) ) {
				DrawCut( cut, 0f );
			}
		}

		ArchPreviews.Load().Draw( ArchKind.Road,
			new ArchPreviewContext( road, null, null, 0f, Picked?.Targets( road ) == true, this ) );
	}

	bool Shown( int floor ) => floor == Level;

	bool Shown( int floor, int itemId ) => OverlayShows( floor, Level, itemId, LayerHidden );

	public static bool OverlayShows( int floor, int level, int itemId, Func<int, bool> hidden ) {
		return floor == level && !(hidden?.Invoke( itemId ) ?? false);
	}

	const int GridMargin = 3;

	void DrawWorkPlane( ArchBuilding building ) {
		var frame = WorkFrame( building );

		if ( !WorkBounds( building, frame, out var min, out var max ) || !Over( frame, min, max ) ) {
			return;
		}

		var grid = MathF.Max( 8f, Kit.GridSize );
		var height = LevelHeight + ArchAsks.Lift( Plan, building, Kit );
		var world = ArchRemap.Spun( frame.Pivot, frame.Degrees );

		Gizmo.Draw.LineThickness = 1f;
		Gizmo.Draw.Color = ArchGhost.Line.WithAlpha( 0.12f );

		for ( var x = min.x; x <= max.x + 0.01f; x += grid ) {
			Rung( world, new Vector2( x, min.y ), new Vector2( x, max.y ), height );
		}

		for ( var y = min.y; y <= max.y + 0.01f; y += grid ) {
			Rung( world, new Vector2( min.x, y ), new Vector2( max.x, y ), height );
		}

		Gizmo.Draw.LineThickness = 2f;
		Gizmo.Draw.Color = ArchGhost.Line.WithAlpha( 0.4f );

		var edge = ArchFootprint.Rect( min, max );

		world.Loop( edge );
		DrawLoop( edge, height );
	}

	static void Rung( ArchRemap world, Vector2 from, Vector2 to, float height ) {
		var start = world.Of( from );
		var end = world.Of( to );

		Gizmo.Draw.Line( new Vector3( start.x, start.y, height ), new Vector3( end.x, end.y, height ) );
	}

	(Vector2 Pivot, float Degrees) WorkFrame( ArchBuilding building ) {
		return Frame( building.Rooms.Where( room => room.Floor == Level || room.Floor == Level - 1 ) );
	}

	bool Over( (Vector2 Pivot, float Degrees) frame, Vector2 min, Vector2 max ) {
		if ( !AimPoint( out var world ) ) {
			return false;
		}

		var point = ArchFootprint.Turned( new Vector2( world.x, world.y ), frame.Pivot, -frame.Degrees );

		return point.x >= min.x && point.y >= min.y
			&& point.x <= max.x && point.y <= max.y;
	}

	// This storey and the one below, in the building's own frame
	bool WorkBounds( ArchBuilding building, (Vector2 Pivot, float Degrees) frame, out Vector2 min, out Vector2 max ) {
		min = default;
		max = default;

		var low = new Vector2( float.MaxValue, float.MaxValue );
		var high = new Vector2( float.MinValue, float.MinValue );
		var found = false;

		foreach ( var room in building.Rooms ) {
			if ( room.Floor != Level && room.Floor != Level - 1 ) {
				continue;
			}

			foreach ( var corner in Outline( room ).Select( point => ArchFootprint.Turned( point, frame.Pivot, -frame.Degrees ) ) ) {
				low = Vector2.Min( low, corner );
				high = Vector2.Max( high, corner );
				found = true;
			}
		}

		if ( !found ) {
			return false;
		}

		var grid = MathF.Max( 8f, Kit.GridSize );
		var margin = grid * GridMargin;

		min = new Vector2(
			MathF.Floor( (low.x - margin) / grid ) * grid,
			MathF.Floor( (low.y - margin) / grid ) * grid );

		max = new Vector2(
			MathF.Ceiling( (high.x + margin) / grid ) * grid,
			MathF.Ceiling( (high.y + margin) / grid ) * grid );

		return true;
	}

	bool Editing( ArchRoom room, ArchRoom inside ) {
		return room == inside || room.Id == ActiveRoomId || Picked?.Targets( room ) == true || Picked?.Room == room;
	}

	void DrawRoom( ArchRoom room, ArchRoom inside, float lift, ArchBuilding building, ArchPreviews previews ) {
		if ( !Shown( room.Floor, room.Id ) ) {
			return;
		}

		var loop = Outline( room );
		var floor = room.BaseHeight + lift;

		if ( room == inside && loop.Count >= 3 ) {
			Gizmo.Draw.LineThickness = 4f;
			Gizmo.Draw.Color = ArchGhost.Line;

			ArchGhost.Ring( loop, floor );

			Gizmo.Draw.LineThickness = 2f;
		}

		if ( !Editing( room, inside ) ) {
			Gizmo.Draw.Color = Color.White.WithAlpha( 0.3f );
			DrawWalls( room, lift );

			return;
		}

		var picked = Picked?.Targets( room ) == true;

		if ( loop.Count >= 3 && (picked || room.Id == ActiveRoomId) ) {
			Gizmo.Draw.Color = picked ? Color.Yellow.WithAlpha( 0.85f ) : ArchGhost.Accent.WithAlpha( 0.45f );
			ArchGhost.Prism( loop, floor, floor + ArchFloorGen.WallHeight( room, Kit ) );
		}

		foreach ( var wall in room.Walls ) {
			if ( LayerHidden( wall.Id ) ) {
				continue;
			}

			Gizmo.Draw.Color = Picked?.Targets( wall ) == true ? Color.Yellow : Color.Cyan;

			Gizmo.Draw.Line(
				new Vector3( wall.Start.x, wall.Start.y, room.BaseHeight + lift ),
				new Vector3( wall.End.x, wall.End.y, room.BaseHeight + lift ) );

			foreach ( var opening in wall.Openings ) {
				if ( LayerHidden( opening.Id ) || LayerHidden( opening.OwnerId ) ) {
					continue;
				}

				var left = wall.PointAt( opening.Left );
				var right = wall.PointAt( opening.Right );
				var z = room.BaseHeight + lift + opening.SillHeight;

				Gizmo.Draw.Color = Picked?.Targets( opening ) == true ? Color.Yellow : Color.Green;
				Gizmo.Draw.Line( new Vector3( left.x, left.y, z ), new Vector3( right.x, right.y, z ) );
			}
		}

		foreach ( var pillar in room.Pillars ) {
			if ( LayerHidden( pillar.Id ) ) {
				continue;
			}

			previews.Draw( ArchKind.Pillar,
				new ArchPreviewContext( pillar, room, building, lift, Picked?.Targets( pillar ) == true ) );
		}

		foreach ( var stair in room.Stairs ) {
			if ( LayerHidden( stair.Id ) ) {
				continue;
			}

			Gizmo.Draw.Color = Picked?.Targets( stair ) == true ? Color.Yellow : Color.Magenta.WithAlpha( 0.6f );
			Gizmo.Draw.LineSphere( new Vector3( stair.Origin.x, stair.Origin.y, stair.BaseHeight + lift ), 10f );

			previews.Draw( ArchKind.Stair,
				new ArchPreviewContext( stair, room, building, lift, Picked?.Targets( stair ) == true, this ) );
		}

		foreach ( var trim in room.Trims ) {
			if ( LayerHidden( trim.Id ) ) {
				continue;
			}

			Gizmo.Draw.Color = Picked?.Targets( trim ) == true ? Color.Yellow : Color.Blue.WithAlpha( 0.7f );

			for ( var index = 0; index < trim.Path.Count - 1; index++ ) {
				Gizmo.Draw.Line( trim.Path[index] + Vector3.Up * lift, trim.Path[index + 1] + Vector3.Up * lift );
			}
		}

		var kinds = ArchKinds.Load();

		foreach ( var porch in room.Porches ) {
			if ( LayerHidden( porch.Id ) ) {
				continue;
			}

			Gizmo.Draw.Color = Picked?.Targets( porch ) == true ? Color.Yellow : ArchGhost.Line;
			var manifest = kinds.Claiming( porch );

			foreach ( var outline in manifest?.Loops( porch ) ?? Array.Empty<List<Vector2>>() ) {
				ArchGhost.Ring( outline, manifest.Height( porch, porch.BaseHeight ) + lift );
			}
		}
	}

	void DrawWalls( ArchRoom room, float lift ) {
		foreach ( var wall in room.Walls ) {
			if ( LayerHidden( wall.Id ) ) {
				continue;
			}

			Gizmo.Draw.Line(
				new Vector3( wall.Start.x, wall.Start.y, room.BaseHeight + lift ),
				new Vector3( wall.End.x, wall.End.y, room.BaseHeight + lift ) );
		}
	}

	public static void DrawRect( Vector2 min, Vector2 max, float height ) {
		DrawLoop( ArchFootprint.Rect( min, max ), height );
	}

	public static void DrawLoop( IReadOnlyList<Vector2> loop, float height ) {
		for ( var index = 0; index < loop.Count; index++ ) {
			var from = loop[index];
			var to = loop[(index + 1) % loop.Count];

			Gizmo.Draw.Line( new Vector3( from.x, from.y, height ), new Vector3( to.x, to.y, height ) );
		}
	}
}