Editor/Data/ArchArchetype.cs
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

// A step names a side, not a wall id - ids are allocated as the plan grows.
public enum ArchSide {
	MinX,
	MaxX,
	MinY,
	MaxY
}

// Every step is one call into an authoring service that already exists; never new geometry.
public enum ArchStepKind {
	Shell,
	Wing,
	Canopy,
	Opening,
	Pillars,
	Monitor,
	Ladder,
	Storey,
	Extrude
}

// Fractions alone can't hold a bay to its real width; inches alone can't fit any plot.
public sealed class ArchPlotRect {
	public Vector2 Min { get; set; } = new( 0f, 0f );
	public Vector2 Max { get; set; } = new( 1f, 1f );
	public Vector2 MinInset { get; set; }
	public Vector2 MaxInset { get; set; }
}

// Null = the authoring service's default, not "off" - "no fascia" without restating every trim.
public sealed class ArchSectionRules {
	public float WallThickness { get; set; }
	public float RoofThickness { get; set; }
	public float WallHeight { get; set; }
	public RoofStyle? Roof { get; set; }
	public RidgeRun Ridge { get; set; } = RidgeRun.Auto;
	public float RoofPitch { get; set; }
	public float Overhang { get; set; }

	public bool? Floor { get; set; }
	// Off = no slab and no plinth; the terrain runs straight through the doors.
	public bool? Foundation { get; set; }
	public bool? Ceiling { get; set; }
	public bool? Gutters { get; set; }
	public bool? Fascia { get; set; }
	public bool? Soffit { get; set; }
	public bool? Frame { get; set; }
	public float FrameSpacing { get; set; }

	public bool FloorBoards { get; set; }
	public float FloorBoardYaw { get; set; }

	// Null leaves the walls as stood - not the same as a bare treatment.
	public ArchWallPreset Wall { get; set; }

	// What the section collides as. A far-off silhouette is set to None from here, so the vista trick is
	// one type on disk rather than a per-room edit repeated across a skyline.
	public ArchCollisionMode? Collision { get; set; }

	// On a flat roof it's the whole silhouette - a factory type must be able to say it.
	public bool? Parapet { get; set; }
	public float ParapetHeight { get; set; }
	// Edge protection round a deck anyone is meant to walk on.
	public bool? Guardrail { get; set; }
	public float GuardHeight { get; set; }
	// Keyed by ArchSurface name, so a skin ships without a map palette loaded.
	public ArchPalette Palette { get; set; } = new();
}

public sealed class ArchArchetypeStep {
	public ArchStepKind Kind { get; set; }
	// Registers the room so a later step can name it; empty acts on the last room.
	public string Name { get; set; }
	public string Target { get; set; }

	public ArchPlotRect Rect { get; set; } = new();
	public ArchSectionRules Rules { get; set; } = new();

	public SectionRoof WingRoof { get; set; } = SectionRoof.Continue;
	public float EaveDrop { get; set; }
	public int SawtoothBays { get; set; }

	public float HeadHeight { get; set; } = 160f;
	public float DeckDrop { get; set; }
	public bool Deck { get; set; }
	public bool Posts { get; set; } = true;
	public bool Braces { get; set; }
	public bool Rafters { get; set; } = true;
	public bool Railings { get; set; }
	public bool Steps { get; set; }
	public float PostSpacing { get; set; }

	public ArchSide Side { get; set; }
	public string Preset { get; set; }
	public float Along { get; set; } = 0.5f;
	public int Count { get; set; } = 1;
	// 0 leaves the preset's own size - a recipe stretches a door it already names.
	public float Width { get; set; }
	public float Height { get; set; }
	public float SillHeight { get; set; }
	public bool? Leaf { get; set; }
	public bool? Cased { get; set; }
	public bool? Glazed { get; set; }
	public bool? StartOpen { get; set; }

	// The band is measured off the target storey's own floor: SillHeight is its foot, Height its head, 0 one storey.
	public ArchExtrudeKind Extrude { get; set; }
	public float Proud { get; set; } = 4f;
	public float Course { get; set; }
	public float Joint { get; set; }

	// Widest pane a Monitor's lid carries before a glazing bar splits it; 0 leaves one sheet.
	public float PaneSpan { get; set; }

	// Spacing is a MAXIMUM bay - the last column lands on the far edge.
	public Vector2 Spacing { get; set; } = new( 240f, 240f );
	public PillarSpan Span { get; set; } = PillarSpan.None;
	// Empty takes the building type's own first kind, so a recipe need not restate it.
	public string Pillar { get; set; } = "";
}

// Declared as data so a new type gates its own panel with no code.
public enum ArchOptionGroup {
	Section,
	Foundation,
	RoofStyle,
	Ridge,
	WingRoof,
	Approach,
	Boundary,
	Yard
}

// The building remembers its type, so a wing added later still obeys it.
public sealed class ArchArchetype {
	public int Version { get; set; } = 1;
	public string Name { get; set; } = "archetype";
	public string Title { get; set; } = "Archetype";
	// A type core ships cannot be deleted, so this is how it is removed: the file says so and the shelf drops it.
	public bool Hidden { get; set; }
	public string Description { get; set; } = "";
	public string Icon { get; set; } = "domain";
	public List<string> Openings { get; set; } = new();
	// Empty offers every kind.
	public List<string> Pillars { get; set; } = new();

	public ArchSectionRules Shell { get; set; } = new();
	public ArchSectionRules Wing { get; set; } = new();
	public SectionRoof WingRoof { get; set; } = SectionRoof.Continue;
	public float WingEaveDrop { get; set; }

	// Empty means every group, so a pre-existing type keeps its panel.
	public List<ArchOptionGroup> Shows { get; set; } = new();

	public bool Wants( ArchOptionGroup group ) => Shows.Count == 0 || Shows.Contains( group );

	public float DriveWidth { get; set; } = 132f;
	public float DriveSplay { get; set; } = 36f;
	public ApproachKind DriveKind { get; set; } = ApproachKind.Ramp;

	public Vector2 MinimumPlot { get; set; } = new( 240f, 240f );
	public List<ArchArchetypeStep> Steps { get; set; } = new();

	public bool HasLayout => Steps.Count > 0;
}