Editor/Carve/ArchCutPart.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

public enum CutProfile {
	Box,
	Steps,
	Passage,
	Poly
}

public enum CutEnclosure {
	None,
	Walls
}

public enum CutHead {
	None,
	Cap,
	Coping,
	Housing
}

public enum ArchDamageKind {
	None,
	Masonry,
	SurfaceSpall,
	MissingPanels,
	DisplacedPanels,
	MixedPanels
}

public enum ArchMasonryPattern {
	ExposedEdge,
	WornCorner,
	BrickPier
}

public enum ArchZoneMode {
	Carve,
	Damage,
	Extrude
}

[Flags]
public enum ArchCutAffects {
	None = 0,
	Floors = 1 << 0,
	Ceilings = 1 << 1,
	Foundations = 1 << 2,
	Walls = 1 << 3,
	WallFittings = 1 << 4,
	Roofs = 1 << 5,
	Platforms = 1 << 6,
	Trims = 1 << 7,
	Gutters = 1 << 8,
	// Only swallows units the hole covers WHOLE
	Windows = 1 << 9,
	Beams = 1 << 10,
	Lining = 1 << 11,
	Roadway = 1 << 12,
	Pillars = 1 << 13,
	Stairs = 1 << 14,
	All = Floors | Ceilings | Foundations | Walls | WallFittings | Roofs | Platforms | Trims | Gutters | Windows | Beams | Lining | Roadway | Pillars | Stairs
}

public sealed class ArchCutSegment {
	public Vector2 Start { get; set; }
	public Vector2 End { get; set; }
	public float Width { get; set; } = 48f;
	public float BaseHeight { get; set; }
	public float TopHeight { get; set; } = 128f;

	public List<Vector2> Loop { get; set; }

	public bool HasLoop => Loop is { Count: >= 3 };

	public Vector2 Span => End - Start;

	public float Length => Span.Length;

	public float Yaw => MathF.Atan2( Span.y, Span.x ).RadianToDegree();

	public ArchStairAxes Axes => new() { Origin = Start, Yaw = Yaw };

	public List<Vector2> Outline() {
		if ( HasLoop ) {
			return Loop;
		}

		var axes = Axes;
		var across = axes.Across * (MathF.Max( 1f, Width ) * 0.5f);

		return new List<Vector2> { Start - across, End - across, End + across, Start + across };
	}

	public ArchCutSegment Copy() {
		return new ArchCutSegment {
			Start = Start,
			End = End,
			Width = Width,
			BaseHeight = BaseHeight,
			TopHeight = TopHeight,
			Loop = Loop?.ToList()
		};
	}
}

public sealed class ArchCutPart : IArchRamped, IArchPainted, IArchNamed {
	public int Id { get; set; }
	public string Name { get; set; } = "Cut";
	// Zero = hand-authored; nonzero = owned by that layer (deleting the owner deletes this cut)
	public int OwnerId { get; set; }
	public int Level { get; set; }
	public CutProfile Profile { get; set; } = CutProfile.Box;
	public ArchCutAffects Affects { get; set; } = ArchCutAffects.All;
	public List<ArchCutSegment> Segments { get; set; } = new();

	public float StepRise { get; set; }
	public float StepGoing { get; set; }

	public bool Ramp { get; set; }
	public float RampFall { get; set; }
	public float RampYaw { get; set; }

	public float Rise { get; set; }
	public bool SnapAngle { get; set; } = true;

	public CutEnclosure Enclosure { get; set; }
	public float WallThickness { get; set; } = 8f;
	public bool Edge { get; set; }
	public float EdgeWidth { get; set; }
	public bool GuardsOpenedEdges { get; set; } = true;
	public bool BreakEdges { get; set; }
	public ArchDamageKind Damage { get; set; }
	public float DamageAmount { get; set; } = 0.55f;
	public float DamageCellWidth { get; set; } = 24f;
	public float DamageCellLength { get; set; } = 48f;
	public float DamageDepth { get; set; } = 4f;
	public ArchMasonryPattern MasonryPattern { get; set; }
	public bool MasonryCarves { get; set; }
	public float DamageDrop { get; set; } = 4f;
	public float DamageTilt { get; set; } = 12f;

	public ArchDamageKind ResolvedDamage => Damage != ArchDamageKind.None
		? Damage
		: Name?.StartsWith( "Damage", StringComparison.OrdinalIgnoreCase ) == true ? ArchDamageKind.Masonry : ArchDamageKind.None;

	public bool IsDamage => ResolvedDamage != ArchDamageKind.None;

	public ArchExtrudeKind Extrude { get; set; }
	public float ExtrudeDepth { get; set; } = 4f;
	public float ExtrudeCourse { get; set; }
	public float ExtrudeJoint { get; set; }
	public bool ExtrudeBothFaces { get; set; }
	public string ExtrudePillarType { get; set; } = "";
	// Null inherits the type's footing; set overrides it
	public PillarFooting? ExtrudeFooting { get; set; }

	public bool IsExtrude => Extrude != ArchExtrudeKind.None;

	public ArchZoneMode Mode => IsDamage
		? ArchZoneMode.Damage
		: IsExtrude ? ArchZoneMode.Extrude : ArchZoneMode.Carve;

	public CutHead Head { get; set; }
	public float HeadHeight { get; set; } = 96f;
	public bool HeadRoof { get; set; } = true;

	public float CopingHeight { get; set; } = 4f;
	public float CopingOversail { get; set; } = 1.5f;

	public ArchPalette Palette { get; set; } = new();

	public bool HasContent => Segments.Any( segment => segment.HasLoop || segment.Length > 1f );

	public bool Builds => Enclosure != CutEnclosure.None || Head != CutHead.None;

	public IEnumerable<ArchCutSegment> Legs() => Segments.Where( segment => segment.HasLoop || segment.Length > 1f );

	public IEnumerable<List<Vector2>> Outlines() => Legs().Select( segment => segment.Outline() );
}