Editor/Wall/ArchWall.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

public enum WallCladding {
	None,
	Weatherboard,
	Profiled
}

// A wall carries a LIST of them - a real elevation has several fields.
public enum BandSeat {
	Centred,
	// A cap finishes a field rather than dividing two, so it sits ON it.
	Above
}

public sealed class ArchWallBand {
	public float Height { get; set; }
	// Relative - a preset naming an absolute height caps only walls that tall.
	public bool AtHead { get; set; }
	// The role the field BELOW this band takes.
	public ArchSurface Field { get; set; } = ArchSurface.WallBase;
	public bool Course { get; set; } = true;
	public BandSeat Seat { get; set; } = BandSeat.Centred;
	// 0 falls back to the kit's own band section.
	public float CourseHeight { get; set; }
	public float CourseDepth { get; set; }
	// A row of blocks under the course. Their pitch is kit stock, so no band carries a count.
	public bool Dentils { get; set; }

	public float At( float wallHeight ) => AtHead ? wallHeight : Height;

	public ArchWallBand Copy() {
		return new ArchWallBand {
			Height = Height,
			AtHead = AtHead,
			Field = Field,
			Course = Course,
			Seat = Seat,
			CourseHeight = CourseHeight,
			CourseDepth = CourseDepth,
			Dentils = Dentils
		};
	}
}

// Marks no change of field, so it can sit anywhere.
public sealed class ArchWallTrim {
	public float Height { get; set; } = 96f;
	// Relative - one treatment is worn by walls of several heights.
	public bool AtHead { get; set; }
	public float Rise { get; set; } = 3f;
	public float Depth { get; set; } = 1.5f;
	public float Bevel { get; set; } = 0.75f;
	public ArchSurface Field { get; set; } = ArchSurface.Trim;
	public bool Interior { get; set; }

	public float At( float wallHeight ) => AtHead ? wallHeight - Rise : Height;

	public ArchWallTrim Copy() {
		return new ArchWallTrim {
			Height = Height,
			AtHead = AtHead,
			Rise = Rise,
			Depth = Depth,
			Bevel = Bevel,
			Field = Field,
			Interior = Interior
		};
	}
}

// A run of expressed strips - one part, not one per strip.
public sealed class ArchPilasterRun {
	public float Bay { get; set; }
	// Wins over Bay when set.
	public int Count { get; set; }
	public float Width { get; set; } = 12f;
	public float Depth { get; set; } = 3f;
	// So a pilaster can die under an eave rather than into it.
	public float Headroom { get; set; }
	// So a column line can stand on a plinth rather than run into the paving.
	public float Foot { get; set; }
	// An authored climb honoured PAST the storey head, so one pier runs every storey to the roof-edge cornice as a
	// single layer. Zero stops it at its own storey's head, less Headroom.
	public float Rise { get; set; }
	// 0 is a smooth shaft; above it the pier is rusticated, blocks Course tall parted by Joint.
	public float Course { get; set; }
	public float Joint { get; set; }
	// A taller, deeper block at the foot and another under whatever the pier dies into.
	public float Plinth { get; set; }
	public float Cap { get; set; }
	// End strips straddle the corner so two elevations wrap one column.
	public bool OnCorners { get; set; } = true;

	public bool Stands => (Bay > 1f || Count > 0) && Width > 0.1f && Depth > 0.05f;

	public ArchPilasterRun Copy() {
		return new ArchPilasterRun {
			Bay = Bay,
			Count = Count,
			Width = Width,
			Depth = Depth,
			Headroom = Headroom,
			Foot = Foot,
			Rise = Rise,
			Course = Course,
			Joint = Joint,
			Plinth = Plinth,
			Cap = Cap,
			OnCorners = OnCorners
		};
	}
}

// A rhythm the whole wall wears, standing real openings rather than deriving holes - a rhythm's window has
// to wear the frame, sash and sill a dragged one wears, and only an authored unit does.
public sealed class ArchOpeningRun {
	public string Preset { get; set; } = "";
	public float Bay { get; set; }
	// Wins over Bay when set.
	public int Count { get; set; }
	// 0 takes the preset's own.
	public float Width { get; set; }
	public float Height { get; set; }
	public float SillHeight { get; set; }

	public bool Stands => !string.IsNullOrWhiteSpace( Preset ) && (Bay > 1f || Count > 0);

	public ArchOpeningRun Copy() {
		return new ArchOpeningRun {
			Preset = Preset,
			Bay = Bay,
			Count = Count,
			Width = Width,
			Height = Height,
			SillHeight = SillHeight
		};
	}
}

// One list of fields, so a setting can't go missing between wall and preset.
public interface IArchWallTreatment {
	float Height { get; set; }
	float Thickness { get; set; }
	bool Exterior { get; set; }
	bool SingleSided { get; set; }
	bool Baseboard { get; set; }
	bool Cap { get; set; }
	float CapHeight { get; set; }
	float CapOverhang { get; set; }
	string Cornice { get; set; }
	WallCladding Cladding { get; set; }
	bool Wainscot { get; set; }
	List<ArchWallBand> Bands { get; set; }
	List<ArchWallTrim> Trims { get; set; }
	ArchPilasterRun Pilasters { get; set; }
	ArchOpeningRun OpeningRun { get; set; }
}

public static class ArchWallTreatments {
	// Copied, not shared - two walls holding one list is one wall's band moving both.
	public static void Dress( IArchWallTreatment source, IArchWallTreatment target ) {
		target.Height = source.Height;
		target.Thickness = source.Thickness;
		target.Exterior = source.Exterior;
		target.SingleSided = source.SingleSided;
		target.Baseboard = source.Baseboard;
		target.Cap = source.Cap;
		target.CapHeight = source.CapHeight;
		target.CapOverhang = source.CapOverhang;
		target.Cornice = source.Cornice;
		target.Cladding = source.Cladding;
		target.Wainscot = source.Wainscot;
		target.Bands = source.Bands.Select( band => band.Copy() ).ToList();
		target.Trims = source.Trims.Select( trim => trim.Copy() ).ToList();
		target.Pilasters = source.Pilasters.Copy();
		target.OpeningRun = source.OpeningRun.Copy();
	}

	// The order depends on the wall height, so every caller names it.
	public static IEnumerable<ArchWallBand> Ordered( IEnumerable<ArchWallBand> bands, float wallHeight ) {
		return (bands ?? Enumerable.Empty<ArchWallBand>())
			.Where( band => band.At( wallHeight ) > 0.05f )
			.OrderBy( band => band.At( wallHeight ) );
	}

	// Low edge first; one clearing the plate belongs to the elevation above.
	public static IEnumerable<ArchWallTrim> Standing( IEnumerable<ArchWallTrim> trims, float wallHeight ) {
		return (trims ?? Enumerable.Empty<ArchWallTrim>())
			.Where( trim => trim.Rise > 0.05f && trim.Depth > 0.05f )
			.Where( trim => trim.At( wallHeight ) >= 0f && trim.At( wallHeight ) + trim.Rise <= wallHeight + 0.05f )
			.OrderBy( trim => trim.At( wallHeight ) );
	}
}

public sealed class ArchWall : IArchWallTreatment, IArchPainted {
	public int Id { get; set; }
	public Vector2 Start { get; set; }
	public Vector2 End { get; set; }
	public float Height { get; set; }
	public float Thickness { get; set; }
	public bool Exterior { get; set; } = true;
	// WHETHER the far face exists, where Exterior only decides which SKIN each face takes. A silhouette
	// filling the space between the viewer and the landmark is never entered and never seen from behind,
	// so half its triangles are paying for nothing.
	public bool SingleSided { get; set; }
	public bool Baseboard { get; set; } = true;
	public bool Cap { get; set; }
	// 0 falls back to the kit's own coping section.
	public float CapHeight { get; set; }
	// How far the coping oversails each face - a parapet reads as brick because it stands proud of both.
	public float CapOverhang { get; set; }
	// The entablature the head wears, by name off the kit's shelf. Empty is a bare head; a style that stands
	// finishes the wall itself, so the plain coping steps aside for it.
	public string Cornice { get; set; } = "";
	public WallCladding Cladding { get; set; }
	public bool Wainscot { get; set; }
	// Rides the SAME z split the openings make - a banded elevation costs no second wall.
	public List<ArchWallBand> Bands { get; set; } = new();
	// Proud strips at heights of their own, marking nothing.
	public List<ArchWallTrim> Trims { get; set; } = new();
	public ArchPilasterRun Pilasters { get; set; } = new();
	public ArchOpeningRun OpeningRun { get; set; } = new();
	public ArchPalette Palette { get; set; } = new();
	public List<ArchOpening> Openings { get; set; } = new();
	// Placed work at a station on this run - a pilaster where the rhythm above puts none, a recess.
	public List<ArchWallModPart> Modifiers { get; set; } = new();
	public float Length => (End - Start).Length;

	public Vector2 Direction => Length < 0.001f ? new Vector2( 1f, 0f ) : (End - Start) / Length;

	public Vector2 Normal {
		get {
			var direction = Direction;
			return new Vector2( -direction.y, direction.x );
		}
	}

	public Vector2 PointAt( float distance ) => Start + Direction * distance;
}