#include "io/SceneryCatalog.h"
#include <cmath>
#include <vector>

namespace MC {

SceneryCatalog& GetSceneryCatalog() {
    static SceneryCatalog instance;
    if (!instance.IsLoaded())
        instance.LoadBuiltIn();
    return instance;
}

// ── Color helpers ─────────────────────────────────────────────────────────────
static SceneryColor C(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) {
    return {r, g, b, a};
}
static const SceneryColor kPlatformGray   = C(180,175,170);
static const SceneryColor kPlatformEdge   = C(120,115,110);
static const SceneryColor kPlatformYellow = C(220,200,100);  // warning strip
static const SceneryColor kBuildingWall   = C(220,200,170);  // sandstone
static const SceneryColor kBuildingRoof   = C(140, 80, 60);  // terracotta
static const SceneryColor kBuildingDark   = C(100, 80, 60);  // dark brick
static const SceneryColor kWindowBlue     = C(160,190,220);
static const SceneryColor kDoorBrown      = C(100, 70, 40);
static const SceneryColor kRoadGray       = C(100,100,100);
static const SceneryColor kRoadLine       = C(240,220,  0);  // yellow road marking
static const SceneryColor kRoadWhite      = C(240,240,240);
static const SceneryColor kGrass          = C(100,160, 60);
static const SceneryColor kTreeGreen      = C( 60,130, 50);
static const SceneryColor kTreeDark       = C( 40,100, 35);
static const SceneryColor kTreeConifer    = C( 30, 90, 40);
static const SceneryColor kTrunk          = C(100, 70, 40);
static const SceneryColor kCarRed         = C(200, 50, 40);
static const SceneryColor kCarGray        = C(160,160,160);
static const SceneryColor kCarWindow      = C(180,210,230);
static const SceneryColor kMetalGray      = C(150,150,155);
static const SceneryColor kOutline        = C(  0,  0,  0);
static const SceneryColor kOutlineSoft    = C( 60, 60, 60);
static const SceneryColor kTurntableGray  = C(130,130,135);
static const SceneryColor kSteelBlue      = C( 80,100,140);
static const SceneryColor kWoodBrown      = C(150,110, 60);
static const SceneryColor kConcreteGray   = C(170,165,160);

// ── Shape builder helpers ─────────────────────────────────────────────────────
static SceneryShape Rect(double x, double y, double w, double h,
                          SceneryColor fill, SceneryColor stroke = {60,60,60,255},
                          int strokePx = 1)
{
    SceneryShape s;
    s.type     = SceneryShapeType::FilledRect;
    s.fill     = fill;
    s.stroke   = stroke;
    s.strokePx = strokePx;
    s.x = x; s.y = y; s.w = w; s.h = h;
    return s;
}

static SceneryShape Ellipse(double x, double y, double w, double h,
                             SceneryColor fill, SceneryColor stroke = {60,60,60,255},
                             int strokePx = 1)
{
    SceneryShape s;
    s.type     = SceneryShapeType::FilledEllipse;
    s.fill     = fill;
    s.stroke   = stroke;
    s.strokePx = strokePx;
    s.x = x; s.y = y; s.w = w; s.h = h;
    return s;
}

static SceneryShape OutRect(double x, double y, double w, double h,
                             SceneryColor stroke, int strokePx = 1)
{
    SceneryShape s;
    s.type     = SceneryShapeType::OutlineRect;
    s.fill     = {0,0,0,0};
    s.stroke   = stroke;
    s.strokePx = strokePx;
    s.x = x; s.y = y; s.w = w; s.h = h;
    return s;
}

static SceneryShape Poly(std::vector<Vec2> pts,
                          SceneryColor fill, SceneryColor stroke = {60,60,60,255},
                          int strokePx = 1)
{
    SceneryShape s;
    s.type     = SceneryShapeType::FilledPolygon;
    s.fill     = fill;
    s.stroke   = stroke;
    s.strokePx = strokePx;
    s.pts      = std::move(pts);
    return s;
}

static SceneryShape Line(double x1, double y1, double x2, double y2,
                          SceneryColor col, int strokePx = 1)
{
    SceneryShape s;
    s.type     = SceneryShapeType::Line;
    s.fill     = col;
    s.stroke   = col;
    s.strokePx = strokePx;
    s.x = x1; s.y = y1; s.w = x2; s.h = y2;
    return s;
}

// Text label rendered at (cx, cy) center, with font height fontSizeMm in mm.
static SceneryShape Text(double cx, double cy, double fontSizeMm,
                          const std::string& text, SceneryColor color)
{
    SceneryShape s;
    s.type    = SceneryShapeType::Text;
    s.fill    = color;
    s.stroke  = {0,0,0,0};
    s.strokePx = 0;
    s.x = cx; s.y = cy; s.w = 0; s.h = fontSizeMm;
    s.textStr = text;
    return s;
}

// ── LoadBuiltIn ───────────────────────────────────────────────────────────────
void SceneryCatalog::LoadBuiltIn()
{
    m_items.clear();
    m_index.clear();

    auto Add = [&](SceneryItem item) {
        m_index[item.id] = m_items.size();
        m_items.push_back(std::move(item));
    };

    // ── PLATFORMS ─────────────────────────────────────────────────────────────

    // Standard platform -- 230 x 60mm (resizable via corner/edge handles)
    {
        SceneryItem p;
        p.id = "platform"; p.displayName = "Platform";
        p.category = SceneryCategory::Platform;
        p.widthMm = 230; p.heightMm = 60;
        // Base slab
        p.shapes.push_back(Rect(0, 0, 230, 60, kPlatformGray, kPlatformEdge));
        // Yellow warning strip along track edge (bottom edge = trackside)
        p.shapes.push_back(Rect(0, 52, 230, 8, kPlatformYellow, {0,0,0,0}, 0));
        // Surface texture lines
        p.shapes.push_back(Line( 40, 0,  40, 60, {150,145,140,80}, 1));
        p.shapes.push_back(Line( 80, 0,  80, 60, {150,145,140,80}, 1));
        p.shapes.push_back(Line(120, 0, 120, 60, {150,145,140,80}, 1));
        p.shapes.push_back(Line(160, 0, 160, 60, {150,145,140,80}, 1));
        p.shapes.push_back(Line(190, 0, 190, 60, {150,145,140,80}, 1));
        Add(p);
    }

    // Island platform -- 460 x 120mm (yellow strips both sides)
    {
        SceneryItem p;
        p.id = "platform_island"; p.displayName = "Island platform 460mm";
        p.category = SceneryCategory::Platform;
        p.widthMm = 460; p.heightMm = 120;
        p.shapes.push_back(Rect(0, 0, 460, 120, kPlatformGray, kPlatformEdge));
        // Yellow warning strips top and bottom
        p.shapes.push_back(Rect(0,   0, 460, 8, kPlatformYellow, {0,0,0,0}, 0));
        p.shapes.push_back(Rect(0, 112, 460, 8, kPlatformYellow, {0,0,0,0}, 0));
        // Centre walkway marking
        p.shapes.push_back(Rect(0, 54, 460, 12, C(200,195,190), {0,0,0,0}, 0));
        for (double x = 40; x < 460; x += 40)
            p.shapes.push_back(Line(x, 0, x, 120, {150,145,140,80}, 1));
        // Shelter roof outline (centre third)
        p.shapes.push_back(OutRect(115, 20, 230, 80, C(80,80,80,180), 2));
        Add(p);
    }

    // ── BUILDINGS ─────────────────────────────────────────────────────────────

    // Güglingen-style small station -- 308 x 140mm
    // Two-storey main house with steep gable, plus an open platform shelter
    // on the left and a low freight/baggage wing on the right.
    {
        SceneryItem p;
        p.id = "station_small"; p.displayName = "Station Gueglingen";
        p.category = SceneryCategory::Station;
        p.widthMm = 308; p.heightMm = 140;

        SceneryColor wallCream  = C(235, 215, 175);  // stucco
        SceneryColor wingCream  = C(220, 195, 155);  // slightly darker (freight wing)
        SceneryColor roofMain   = C(150, 70, 55);    // terracotta
        SceneryColor roofShade  = C(110, 50, 40);
        SceneryColor freightRf  = C(110, 105, 110);  // grey slate on freight wing
        SceneryColor shutterBlue= C(110, 130, 145);  // blue-grey shutters
        SceneryColor doorBrown  = C( 95, 65, 40);
        SceneryColor signWhite  = C(245, 240, 225);

        // --- Platform shelter (X=0..70) - open canopy ---
        // A thin deck under the canopy
        p.shapes.push_back(Rect(0, 90, 70, 50, C(180,175,170), kOutlineSoft));
        // Roof of canopy (gentle pitch)
        p.shapes.push_back(Poly({{0,80},{70,80},{60,70},{10,70}}, freightRf, kOutlineSoft));
        // 3 support posts
        p.shapes.push_back(Rect( 8, 90, 4, 50, doorBrown, doorBrown));
        p.shapes.push_back(Rect(33, 90, 4, 50, doorBrown, doorBrown));
        p.shapes.push_back(Rect(58, 90, 4, 50, doorBrown, doorBrown));

        // --- Main building (X=70..195, Y=20..140) ---
        // Two-storey body
        p.shapes.push_back(Rect(70, 20, 125, 120, wallCream, kOutlineSoft));
        // Steep gable roof (ridge runs front-back, perpendicular to X) -- the
        // peak comes forward; draw as a tall trapezoid behind, then a forward
        // cross-gable triangle.
        p.shapes.push_back(Poly({{70,20},{195,20},{182,5},{83,5}}, roofMain, kOutlineSoft));
        // Cross-gable peak coming forward (centered on the front)
        p.shapes.push_back(Poly({{105,140},{160,140},{145,80},{120,80}}, roofMain, kOutlineSoft));
        p.shapes.push_back(Poly({{120,80},{145,80},{132,50}}, roofShade, kOutlineSoft));
        // Roof ridge accent
        p.shapes.push_back(Line(83, 12, 182, 12, roofShade, 2));
        // Two chimneys on the ridge
        p.shapes.push_back(Rect(110,  4, 7, 14, kBuildingDark, kOutline));
        p.shapes.push_back(Rect(155,  4, 7, 14, kBuildingDark, kOutline));
        // Top-floor windows in the cross-gable
        p.shapes.push_back(Rect(120, 88, 22, 18, kWindowBlue, kOutlineSoft));
        // Round attic vent
        p.shapes.push_back(Ellipse(124, 70, 16, 16, kBuildingDark, kOutline, 1));
        // Upper-floor windows (3 across) with shutters
        for (double x = 80; x < 195; x += 38) {
            p.shapes.push_back(Rect(x,    72, 18, 26, kWindowBlue, kOutlineSoft));
            p.shapes.push_back(Rect(x-5,  72,  5, 26, shutterBlue, kOutlineSoft));
            p.shapes.push_back(Rect(x+18, 72,  5, 26, shutterBlue, kOutlineSoft));
        }
        // Station sign band (centered, just below upper floor)
        p.shapes.push_back(Rect(108, 105, 50, 12, signWhite, kOutlineSoft));
        // Ground-floor: 3 doors with arched tops, slim windows above each door
        for (int i = 0; i < 3; ++i) {
            double x = 78 + i * 38.0;
            p.shapes.push_back(Rect(x, 120, 28, 20, kWindowBlue, kOutlineSoft));
            p.shapes.push_back(Rect(x+4, 120, 20, 20, doorBrown, kOutlineSoft));
        }

        // --- Right freight wing (X=195..308, Y=60..140) ---
        p.shapes.push_back(Rect(195, 60, 113, 80, wingCream, kOutlineSoft));
        // Lower gable roof (ridge along X, grey slate)
        p.shapes.push_back(Poly({{195,60},{308,60},{295,48},{208,48}}, freightRf, kOutlineSoft));
        p.shapes.push_back(Line(208, 54, 295, 54, kOutlineSoft, 1));
        // Chimney on freight wing
        p.shapes.push_back(Rect(285, 46, 6, 12, kBuildingDark, kOutline));
        // Two trackside windows
        p.shapes.push_back(Rect(212, 100, 22, 30, kWindowBlue, kOutlineSoft));
        p.shapes.push_back(Rect(260, 100, 22, 30, kWindowBlue, kOutlineSoft));
        // Centered door (loading door)
        p.shapes.push_back(Rect(241, 105, 20, 35, doorBrown, kOutlineSoft));
        // Small platform skirt in front of freight wing
        p.shapes.push_back(Rect(195, 138, 113, 4, kPlatformEdge, kPlatformEdge));

        Add(p);
    }

    // Schwarzburg-style large station -- 550 x 185mm
    // Long building with left annex, central clock tower with steep spire,
    // main half-timbered hall, and a covered platform shed on the right.
    {
        SceneryItem p;
        p.id = "station_large"; p.displayName = "Station Schwarzburg";
        p.category = SceneryCategory::Station;
        p.widthMm = 550; p.heightMm = 185;

        // Roof color variants for visual interest
        SceneryColor roofMain   = C(135, 70, 60);   // weathered terracotta
        SceneryColor roofDark   = C(100, 50, 45);   // shadowed roof
        SceneryColor halfTimber = C( 90, 60, 40);   // dark wooden beams
        SceneryColor wallCream  = C(230, 215, 185); // half-timbered infill
        SceneryColor towerStone = C(190, 180, 165); // tower base
        SceneryColor spireDark  = C( 80, 45, 35);   // tower spire

        // --- Ground footprint (back to front) ---
        // Whole footprint as a single light wall slab, will be overdrawn by
        // wings and roof above.
        p.shapes.push_back(Rect(0, 30, 550, 155, wallCream, kOutlineSoft));

        // --- Left annex (X=0..130) ---
        // Body
        p.shapes.push_back(Rect(0, 70, 130, 115, towerStone, kOutlineSoft));
        // Hipped roof (drawn as overlapping trapezoid)
        p.shapes.push_back(Poly({{0,70},{130,70},{115,55},{15,55}}, roofMain, kOutlineSoft));
        // Half-timber detail on roof edge
        p.shapes.push_back(Rect(8, 90, 114, 4, halfTimber, halfTimber));
        // Small ground-floor windows
        for (double x = 12; x < 120; x += 22)
            p.shapes.push_back(Rect(x, 130, 12, 30, kWindowBlue, kOutlineSoft));

        // --- Clock tower (X=110..170) - sticks forward, very tall ---
        p.shapes.push_back(Rect(110, 40, 60, 130, towerStone, kOutlineSoft));
        // Steep spire (tall triangle reaching well above other roofs)
        p.shapes.push_back(Poly({{110,40},{170,40},{140,0}}, spireDark, kOutlineSoft));
        // Tower windows (narrow vertical slits)
        p.shapes.push_back(Rect(125, 60, 8, 25, kWindowBlue, kOutlineSoft));
        p.shapes.push_back(Rect(147, 60, 8, 25, kWindowBlue, kOutlineSoft));
        // Clock face near top
        p.shapes.push_back(Ellipse(125, 95, 30, 30, C(240,235,210), kOutline, 1));
        // Half-timber detail
        p.shapes.push_back(Rect(110, 130, 60, 4, halfTimber, halfTimber));

        // --- Main building (X=170..400) - steep gable, dormers ---
        // Body (wallCream is already painted; outline the section)
        p.shapes.push_back(Rect(170, 20, 230, 165, wallCream, kOutlineSoft));
        // Steep main gable roof (very tall in real model -- represent as
        // tall trapezoid that nearly reaches the top of canvas)
        p.shapes.push_back(Poly({{170,20},{400,20},{380,0},{190,0}}, roofMain, kOutlineSoft));
        // Roof ridge accent line
        p.shapes.push_back(Line(190, 5, 380, 5, roofDark, 2));
        // Two dormer windows on the front roof slope
        p.shapes.push_back(Rect(220, 30, 22, 18, wallCream, kOutlineSoft));
        p.shapes.push_back(Rect(220, 30, 22, 6,  roofMain,  kOutlineSoft));
        p.shapes.push_back(Rect(308, 30, 22, 18, wallCream, kOutlineSoft));
        p.shapes.push_back(Rect(308, 30, 22, 6,  roofMain,  kOutlineSoft));
        // Two chimneys on roof
        p.shapes.push_back(Rect(250, 12, 8, 14, kBuildingDark, kOutline));
        p.shapes.push_back(Rect(345, 12, 8, 14, kBuildingDark, kOutline));
        // Half-timber framework (cross beams forming X-pattern in middle)
        for (double x = 178; x < 400; x += 28) {
            p.shapes.push_back(Line(x, 60, x, 175, halfTimber, 2));
        }
        // Horizontal cross-beams
        p.shapes.push_back(Line(170, 95,  400, 95,  halfTimber, 2));
        p.shapes.push_back(Line(170, 130, 400, 130, halfTimber, 2));
        // Upper-floor windows (5 windows across)
        for (double x = 185; x < 395; x += 42)
            p.shapes.push_back(Rect(x, 65, 26, 25, kWindowBlue, kOutlineSoft));
        // Ground-floor windows / door
        for (double x = 185; x < 280; x += 35)
            p.shapes.push_back(Rect(x, 135, 22, 38, kWindowBlue, kOutlineSoft));
        // Main entrance (centered, with little awning)
        p.shapes.push_back(Rect(282, 125, 35, 60, kDoorBrown, kOutlineSoft));
        p.shapes.push_back(Poly({{275,125},{325,125},{315,115},{285,115}}, roofDark, kOutlineSoft));
        for (double x = 322; x < 395; x += 35)
            p.shapes.push_back(Rect(x, 135, 22, 38, kWindowBlue, kOutlineSoft));

        // --- Right wing: covered platform shed (X=400..550) ---
        // Back wall (the part with offices/waiting room)
        p.shapes.push_back(Rect(490, 60, 60, 125, towerStone, kOutlineSoft));
        // Open colonnade body (lighter -- columns will be drawn over it)
        p.shapes.push_back(Rect(400, 70, 90, 115, C(200,190,170), kOutlineSoft));
        // Lower hipped roof over the whole right wing
        p.shapes.push_back(Poly({{400,70},{550,70},{535,50},{415,50}}, roofMain, kOutlineSoft));
        // Half-timber roof edge
        p.shapes.push_back(Rect(400, 88, 150, 4, halfTimber, halfTimber));
        // Schwarzburg sign band (white plate above the colonnade)
        p.shapes.push_back(Rect(410, 92, 75, 12, C(240,230,210), kOutlineSoft));
        // Colonnade columns (vertical posts)
        for (double x = 408; x < 488; x += 14)
            p.shapes.push_back(Rect(x, 105, 4, 80, kBuildingDark, kBuildingDark));
        // Office window in right end
        p.shapes.push_back(Rect(500, 110, 18, 30, kWindowBlue, kOutlineSoft));
        p.shapes.push_back(Rect(525, 110, 18, 30, kWindowBlue, kOutlineSoft));
        // Right-end chimney
        p.shapes.push_back(Rect(520, 38, 8, 14, kBuildingDark, kOutline));

        Add(p);
    }

    // Signal box — 60 × 50mm  (elevated cab style)
    {
        SceneryItem p;
        p.id = "signal_box"; p.displayName = "Signal box";
        p.category = SceneryCategory::Building;
        p.widthMm = 60; p.heightMm = 50;
        // Ground floor (brick)
        p.shapes.push_back(Rect(5, 25, 50, 25, kBuildingDark, kOutlineSoft));
        // Upper cab (mostly windows)
        p.shapes.push_back(Rect(0, 5, 60, 22, kBuildingWall, kOutlineSoft));
        // Roof
        p.shapes.push_back(Poly({{0,5},{60,5},{55,0},{5,0}}, kBuildingRoof, kOutlineSoft));
        // Cab windows (wrap-around)
        p.shapes.push_back(Rect( 3, 7, 12, 16, kWindowBlue, kOutlineSoft));
        p.shapes.push_back(Rect(18, 7, 12, 16, kWindowBlue, kOutlineSoft));
        p.shapes.push_back(Rect(33, 7, 12, 16, kWindowBlue, kOutlineSoft));
        p.shapes.push_back(Rect(48, 7, 12, 16, kWindowBlue, kOutlineSoft));
        // Ground floor door
        p.shapes.push_back(Rect(22, 35, 16, 15, kDoorBrown, kOutlineSoft));
        Add(p);
    }

    // Engine shed 1-road — 300 × 80mm
    {
        SceneryItem p;
        p.id = "shed_1road"; p.displayName = "Engine shed (1 road)";
        p.category = SceneryCategory::Building;
        p.widthMm = 300; p.heightMm = 80;
        p.shapes.push_back(Rect(0, 0, 300, 80, kConcreteGray, kOutlineSoft));
        // Roof ridge line
        p.shapes.push_back(Poly({{0,0},{300,0},{300,8},{0,8}}, kBuildingDark, kOutlineSoft));
        // Track opening at each end
        p.shapes.push_back(Rect(  0, 20, 12, 42, C(30,30,30), {0,0,0,0}, 0));
        p.shapes.push_back(Rect(288, 20, 12, 42, C(30,30,30), {0,0,0,0}, 0));
        // Side windows
        for (double x = 30; x < 280; x += 50)
            p.shapes.push_back(Rect(x, 15, 25, 20, kWindowBlue, kOutlineSoft));
        // Side door
        p.shapes.push_back(Rect(130, 45, 40, 35, kWoodBrown, kOutlineSoft));
        Add(p);
    }

    // Engine shed 2-road — 300 × 160mm
    {
        SceneryItem p;
        p.id = "shed_2road"; p.displayName = "Engine shed (2 roads)";
        p.category = SceneryCategory::Building;
        p.widthMm = 300; p.heightMm = 160;
        p.shapes.push_back(Rect(0, 0, 300, 160, kConcreteGray, kOutlineSoft));
        p.shapes.push_back(Poly({{0,0},{300,0},{300,8},{0,8}}, kBuildingDark, kOutlineSoft));
        // Two track openings each end
        p.shapes.push_back(Rect(  0,  20, 12, 42, C(30,30,30), {0,0,0,0}, 0));
        p.shapes.push_back(Rect(  0, 100, 12, 42, C(30,30,30), {0,0,0,0}, 0));
        p.shapes.push_back(Rect(288,  20, 12, 42, C(30,30,30), {0,0,0,0}, 0));
        p.shapes.push_back(Rect(288, 100, 12, 42, C(30,30,30), {0,0,0,0}, 0));
        // Dividing wall line
        p.shapes.push_back(Line(0, 80, 300, 80, {100,100,100,200}, 2));
        // Windows each side
        for (double x = 30; x < 280; x += 50) {
            p.shapes.push_back(Rect(x,  15, 25, 20, kWindowBlue, kOutlineSoft));
            p.shapes.push_back(Rect(x,  95, 25, 20, kWindowBlue, kOutlineSoft));
        }
        Add(p);
    }

    // Goods shed — 200 × 80mm
    {
        SceneryItem p;
        p.id = "goods_shed"; p.displayName = "Goods shed";
        p.category = SceneryCategory::Building;
        p.widthMm = 200; p.heightMm = 80;
        p.shapes.push_back(Rect(0, 0, 200, 80, kWoodBrown, kOutlineSoft));
        // Roof overhang
        p.shapes.push_back(Poly({{-5,0},{205,0},{205,12},{-5,12}}, kBuildingDark, kOutlineSoft));
        // Loading bay doors (large)
        p.shapes.push_back(Rect(10,  25, 50, 55, C(60,45,30), kOutlineSoft));
        p.shapes.push_back(Rect(75,  25, 50, 55, C(60,45,30), kOutlineSoft));
        p.shapes.push_back(Rect(140, 25, 50, 55, C(60,45,30), kOutlineSoft));
        // Door tracks/rails
        p.shapes.push_back(Line(10, 24, 60,  24, kMetalGray, 2));
        p.shapes.push_back(Line(75, 24, 125, 24, kMetalGray, 2));
        Add(p);
    }

    // Warehouse — 400 × 200mm
    {
        SceneryItem p;
        p.id = "warehouse"; p.displayName = "Warehouse";
        p.category = SceneryCategory::Building;
        p.widthMm = 400; p.heightMm = 200;
        p.shapes.push_back(Rect(0, 0, 400, 200, kConcreteGray, kOutlineSoft));
        // Sawtooth roof profile (skylights)
        for (int i = 0; i < 4; i++) {
            double x0 = i * 100.0;
            p.shapes.push_back(Poly({{x0,0},{x0+100,0},{x0+100,20},{x0+80,5},{x0,20}},
                                    kBuildingDark, kOutlineSoft));
            // Skylight glass
            p.shapes.push_back(Rect(x0+2, 5, 75, 12, kWindowBlue, {0,0,0,0}, 0));
        }
        // Loading doors
        for (double x = 20; x < 380; x += 90)
            p.shapes.push_back(Rect(x, 120, 60, 80, C(60,50,40), kOutlineSoft));
        // Windows upper
        for (double x = 15; x < 390; x += 45)
            p.shapes.push_back(Rect(x, 35, 28, 22, kWindowBlue, kOutlineSoft));
        Add(p);
    }

    // ── SIGNALS ───────────────────────────────────────────────────────────────
    // Six HO-scale variants (C/K/TRIX) plus six Z-scale variants. Each item
    // represents one signal in a specific aspect (red = STOP, green = CLEAR
    // for color light & semaphore; yellow expects-stop / expects-clear for
    // distant). The 3D mesh draws the appropriate physical signal.

    // Build a signal catalog item with a glyph that conveys type at a glance:
    //   semaphore  -- vertical mast with an angled arm at top
    //   color      -- vertical bar with two stacked dots (red/green lights)
    //   distant    -- yellow disc with a horizontal/angled bar
    // All signals also get a small "S" badge so the user can identify them
    // unambiguously regardless of glyph orientation.

    // 2D representation for all signals: a single labeled circle the size of
    // the footprint, with a tiny colored dot showing aspect (red = stop,
    // green = clear).
    //   * SS = Semaphore (no arrow; semaphore arms have no inherent facing
    //     direction at the abstraction level used in plan view).
    //   * SL = Color Light (with arrow showing facing direction).
    auto BuildSignalIcon = [&](SceneryItem& p, const std::string& label,
                                SceneryColor aspect, double sz, bool withArrow)
    {
        SceneryColor ring = C(20, 20, 20);
        SceneryColor fill = C(255, 255, 255);
        p.shapes.push_back(Ellipse(0, 0, sz, sz, fill, ring, 1));

        // Tiny aspect dot in the bottom-right of the circle
        double dotD = sz * 0.16;
        p.shapes.push_back(Ellipse(sz * 0.74, sz * 0.74, dotD, dotD,
                                    aspect, ring, 1));

        // Arrow inside the circle, pointing left (local -X). Bottom band so
        // it doesn't fight the label.
        if (withArrow) {
            double aTip  = sz * 0.10;
            double aTail = sz * 0.32;
            double aH    = sz * 0.08;
            double aY    = sz * 0.78;
            p.shapes.push_back(Poly({
                {aTip,  aY},
                {aTail, aY - aH},
                {aTail, aY + aH}
            }, ring, ring));
            p.shapes.push_back(Line(aTail, aY, sz * 0.55, aY, ring, 1));
        }

        // Label centered on the circle. Base size 10 (compatible with the
        // standard label coefficient of 11 used elsewhere, but tuned to fit
        // a 20mm circle without crowding the aspect dot / arrow).
        p.shapes.push_back(Text(sz * 0.5, sz * 0.42,
                                 10.0, label, C(20, 20, 20)));
    };

    auto AddSemaphoreSignal = [&](const std::string& id, const std::string& name,
                                   bool clearAspect, bool isZScale)
    {
        SceneryItem p;
        p.id = id; p.displayName = name;
        p.category = SceneryCategory::Signal;
        // 20mm circle gives the text room without crowding the edge.
        double sz = isZScale ? 12.0 : 20.0;
        p.widthMm  = sz; p.heightMm = sz;
        p.zScale = isZScale;
        SceneryColor aspect = clearAspect ? C(60, 200, 80) : C(220, 40, 40);
        BuildSignalIcon(p, "SS", aspect, sz, /*withArrow=*/false);
        Add(p);
    };

    auto AddColorSignal = [&](const std::string& id, const std::string& name,
                               bool clearAspect, bool isZScale)
    {
        SceneryItem p;
        p.id = id; p.displayName = name;
        p.category = SceneryCategory::Signal;
        double sz = isZScale ? 12.0 : 20.0;
        p.widthMm  = sz; p.heightMm = sz;
        p.zScale = isZScale;
        SceneryColor aspect = clearAspect ? C(60, 200, 80) : C(220, 40, 40);
        BuildSignalIcon(p, "SL", aspect, sz, /*withArrow=*/true);
        Add(p);
    };

    // HO scale (Marklin C, K, TRIX C)
    AddSemaphoreSignal("signal_semaphore_stop",  "Semaphore (Stop)",   false, false);
    AddSemaphoreSignal("signal_semaphore_clear", "Semaphore (Clear)",  true,  false);
    AddColorSignal    ("signal_color_stop",      "Color light (Stop)", false, false);
    AddColorSignal    ("signal_color_clear",     "Color light (Clear)",true,  false);

    // Z scale (Marklin Z-Track)
    AddSemaphoreSignal("signal_z_semaphore_stop",  "Semaphore Z (Stop)",   false, true);
    AddSemaphoreSignal("signal_z_semaphore_clear", "Semaphore Z (Clear)",  true,  true);
    AddColorSignal    ("signal_z_color_stop",      "Color light Z (Stop)", false, true);
    AddColorSignal    ("signal_z_color_clear",     "Color light Z (Clear)",true,  true);

    // ── BRIDGES ───────────────────────────────────────────────────────────────
    // Decorative bridge scenery -- visually like the C-Track truss and arch
    // bridges but with no embedded track. Users can route any track over them.

    // Truss bridge -- 188 x 64mm (matches Marklin 74620 truss geometry)
    {
        SceneryItem p;
        p.id = "bridge_truss_scenery";
        p.displayName = "Truss bridge (scenery)";
        p.category = SceneryCategory::Bridge;
        p.widthMm = 188; p.heightMm = 64;

        SceneryColor deck   = C(110, 115, 120);
        SceneryColor steel  = C(140, 145, 152);
        SceneryColor steelD = C( 95, 100, 108);

        // Deck plate
        p.shapes.push_back(Rect(0, 6, 188, 52, deck, kOutlineSoft));
        // Two side trusses (visible bands along the long edges)
        p.shapes.push_back(Rect(0,  0, 188, 6, steelD, steelD));
        p.shapes.push_back(Rect(0, 58, 188, 6, steelD, steelD));
        // K-pattern diagonals on the upper truss (just lines)
        for (int i = 0; i < 6; ++i) {
            double x0 = i * (188.0 / 6.0);
            double xm = x0 + 188.0 / 12.0;
            double x1 = x0 + 188.0 / 6.0;
            p.shapes.push_back(Line(x0, 0,  xm, 3, steel, 1));
            p.shapes.push_back(Line(x1, 0,  xm, 3, steel, 1));
            p.shapes.push_back(Line(x0, 64, xm, 61, steel, 1));
            p.shapes.push_back(Line(x1, 64, xm, 61, steel, 1));
        }
        Add(p);
    }

    // Arch bridge -- 360 x 64mm (matches Marklin 74636 arch geometry)
    {
        SceneryItem p;
        p.id = "bridge_arch_scenery";
        p.displayName = "Arch bridge (scenery)";
        p.category = SceneryCategory::Bridge;
        p.widthMm = 360; p.heightMm = 64;

        SceneryColor deck   = C(110, 115, 120);
        SceneryColor steel  = C(140, 145, 152);
        SceneryColor steelL = C(175, 178, 184);

        // Deck plate
        p.shapes.push_back(Rect(0, 14, 360, 36, deck, kOutlineSoft));
        // Arch ribs traced as a series of short chord lines along the long
        // edges (top-down view shows arch silhouette as just two parallel lines).
        p.shapes.push_back(Rect(0,  6, 360, 4, steel, steel));
        p.shapes.push_back(Rect(0, 54, 360, 4, steel, steel));
        // Vertical hangers
        for (int i = 1; i < 8; ++i) {
            double x = i * (360.0 / 8.0);
            p.shapes.push_back(Line(x, 10, x, 14, steelL, 1));
            p.shapes.push_back(Line(x, 50, x, 54, steelL, 1));
        }
        Add(p);
    }

    // ── ROADS ─────────────────────────────────────────────────────────────────

    // Level crossing 1-track — 120 × 80mm (road runs along Y, track along X)
    {
        SceneryItem p;
        p.id = "crossing_1track"; p.displayName = "Level crossing (1 track)";
        p.category = SceneryCategory::Road;
        p.widthMm = 120; p.heightMm = 80;
        // Road surface
        p.shapes.push_back(Rect(0, 0, 120, 80, kRoadGray, kOutlineSoft));
        // Road edges (white lines)
        p.shapes.push_back(Rect(0,  0, 120,  5, kRoadWhite, {0,0,0,0}, 0));
        p.shapes.push_back(Rect(0, 75, 120,  5, kRoadWhite, {0,0,0,0}, 0));
        // Centre line (dashed yellow)
        for (double x = 5; x < 115; x += 20)
            p.shapes.push_back(Rect(x, 37, 12, 5, kRoadLine, {0,0,0,0}, 0));
        // Crossing surface (rubber pads between rails)
        p.shapes.push_back(Rect(0, 24, 120, 14, C(50,50,50), {0,0,0,0}, 0));
        p.shapes.push_back(Rect(0, 42, 120, 14, C(50,50,50), {0,0,0,0}, 0));
        // Warning stripes
        for (int side = 0; side < 2; side++) {
            double ys = side == 0 ? 5 : 55;
            int stripe = 0;
            for (double x = 0; x < 120; x += 16, ++stripe) {
                SceneryColor col = (stripe % 2 == 0) ? kRoadWhite : C(255,150,0);
                p.shapes.push_back(Rect(x, ys, 8, 14, col, {0,0,0,0}, 0));
            }
        }
        Add(p);
    }

    // Level crossing 2-track — 120 × 160mm
    {
        SceneryItem p;
        p.id = "crossing_2track"; p.displayName = "Level crossing (2 tracks)";
        p.category = SceneryCategory::Road;
        p.widthMm = 120; p.heightMm = 160;
        p.shapes.push_back(Rect(0, 0, 120, 160, kRoadGray, kOutlineSoft));
        p.shapes.push_back(Rect(0,   0, 120,  5, kRoadWhite, {0,0,0,0}, 0));
        p.shapes.push_back(Rect(0, 155, 120,  5, kRoadWhite, {0,0,0,0}, 0));
        // Two sets of tracks
        for (int t = 0; t < 2; t++) {
            double yo = t * 80.0;
            p.shapes.push_back(Rect(0, yo+24, 120, 14, C(50,50,50), {0,0,0,0}, 0));
            p.shapes.push_back(Rect(0, yo+42, 120, 14, C(50,50,50), {0,0,0,0}, 0));
        }
        for (double x = 5; x < 115; x += 20)
            p.shapes.push_back(Rect(x, 77, 12, 5, kRoadLine, {0,0,0,0}, 0));
        Add(p);
    }

    // Road straight — 188 × 60mm (matches track length)
    {
        SceneryItem p;
        p.id = "road_straight"; p.displayName = "Road straight 188mm";
        p.category = SceneryCategory::Road;
        p.widthMm = 188; p.heightMm = 60;
        p.shapes.push_back(Rect(0, 0, 188, 60, kRoadGray, kOutlineSoft));
        // Edges
        p.shapes.push_back(Rect(0,  0, 188, 4, C(180,180,180), {0,0,0,0}, 0));
        p.shapes.push_back(Rect(0, 56, 188, 4, C(180,180,180), {0,0,0,0}, 0));
        // Centre dashes
        for (double x = 10; x < 180; x += 28)
            p.shapes.push_back(Rect(x, 27, 16, 6, kRoadLine, {0,0,0,0}, 0));
        Add(p);
    }

    // ── SPECIAL ───────────────────────────────────────────────────────────────

    // Turntable (Märklin 74861) - pit 278mm, deck 263mm.
    // The 2D scenery shape mirrors the track piece's geometry (drawn here
    // when the turntable is rendered as a scenery item; the track-piece
    // path renders separately via Renderer::DrawPiece).
    {
        SceneryItem p;
        p.id = "74861"; p.displayName = "Turntable 74861 (pit 278mm)";
        p.category = SceneryCategory::Special;
        p.widthMm = 278; p.heightMm = 278;
        // Pit (dark circle, 278mm diameter)
        p.shapes.push_back(Ellipse(0, 0, 278, 278, C(60,60,65), C(40,40,40)));
        // Bridge deck (rectangle across centre, 263mm long x 34mm wide,
        // centred in the pit).
        const double pitDiam   = 278.0;
        const double deckLen   = 263.0;
        const double deckWide  = 34.0;
        double deckX = (pitDiam - deckLen)  * 0.5;
        double deckY = (pitDiam - deckWide) * 0.5;
        p.shapes.push_back(Rect(deckX, deckY, deckLen, deckWide,
                                kTurntableGray, kOutlineSoft));
        // Bridge rail highlights
        p.shapes.push_back(Rect(deckX, deckY +  2, deckLen, 5, kMetalGray, {0,0,0,0}, 0));
        p.shapes.push_back(Rect(deckX, deckY + 27, deckLen, 5, kMetalGray, {0,0,0,0}, 0));
        // Centre pivot
        p.shapes.push_back(Ellipse(pitDiam*0.5 - 15, pitDiam*0.5 - 15,
                                   30, 30, kSteelBlue, kOutlineSoft));
        // Pit wall ring
        p.shapes.push_back(OutRect(0, 0, pitDiam, pitDiam, C(80,80,85), 3));
        Add(p);
    }

    // Roundhouse 72886 (Märklin) — 3-stall locomotive shed for the 74862
    // turntable. Three rectangular stalls, each rotated to its own 12°
    // spoke, sharing a common apex at the turntable hub. Each stall is
    // 100 mm wide and 325 mm long (300 mm interior + 25 mm back wall).
    //
    // Stall body frame: x in [R_front, R_back], y in [-w_s/2, +w_s/2].
    //   R_front = 368 mm = pit radius (180) + 24188 stub length (188)
    //   R_back  = 693 mm = R_front + 300 (interior) + 25 (back wall)
    //   w_s     = 100 mm (stall width)
    //
    // Bbox computed from these: 386 wide × 343 deep. The apex (turntable
    // hub) sits 521.3 mm BELOW the bbox centre in local SceneryItem coords.
    // In bbox-local coords (x in [0, widthMm], y in [0, heightMm]):
    //   apex_local = (widthMm/2, -349.6) — outside the bbox, in front.
    {
        SceneryItem p;
        p.id = "72886";
        p.displayName = "Roundhouse 72886 (3-stall)";
        p.category = SceneryCategory::Misc;
        p.widthMm = 386; p.heightMm = 343;

        const SceneryColor kBrick   = C(170, 75, 80);
        const SceneryColor kFrameDk = C( 70, 55, 50);
        const SceneryColor kRoof    = C( 95, 95,100);
        const SceneryColor kDoor    = C(225,225,220);
        const SceneryColor kWindow  = C(170,190,210);

        const double R_front = 368.0;
        const double R_back  = 693.0;
        const double w_s     = 100.0;
        const double cx      = p.widthMm * 0.5;       // bbox-local apex x
        const double apexY   = -349.6;                // bbox-local apex y

        // Convert body-frame (bx, by) at stall angle θ° into bbox-local coords.
        // World formula (apex at origin):
        //   wx = bx*sin θ + by*cos θ
        //   wy = bx*cos θ - by*sin θ
        // Then shift by (cx, -apexY) to put apex at (cx, apexY) in bbox-local.
        auto toLocal = [&](double bx, double by, double thetaDeg) {
            double t = thetaDeg * M_PI / 180.0;
            double s = std::sin(t), c = std::cos(t);
            double wx = bx * s + by * c;
            double wy = bx * c - by * s;
            return Vec2{ cx + wx, apexY + wy };
        };

        // ── Three rectangular stalls (the building body) ──
        for (double theta : { -12.0, 0.0, 12.0 }) {
            std::vector<Vec2> rect = {
                toLocal(R_front, +w_s/2, theta),
                toLocal(R_front, -w_s/2, theta),
                toLocal(R_back,  -w_s/2, theta),
                toLocal(R_back,  +w_s/2, theta),
            };
            p.shapes.push_back(Poly(rect, kBrick, kFrameDk, 2));
        }

        // ── Stall floor strips (track lanes inside each stall) ──
        const SceneryColor kFloor = C(85, 85, 90);
        for (double theta : { -12.0, 0.0, 12.0 }) {
            std::vector<Vec2> strip = {
                toLocal(R_front + 4, +20, theta),
                toLocal(R_front + 4, -20, theta),
                toLocal(R_back  - 4, -20, theta),
                toLocal(R_back  - 4, +20, theta),
            };
            p.shapes.push_back(Poly(strip, kFloor, {0,0,0,0}, 0));
        }

        // ── Doors on the front of each stall ──
        for (double theta : { -12.0, 0.0, 12.0 }) {
            std::vector<Vec2> door = {
                toLocal(R_front - 2, +w_s/2 - 8, theta),
                toLocal(R_front - 2, -w_s/2 + 8, theta),
                toLocal(R_front + 8, -w_s/2 + 8, theta),
                toLocal(R_front + 8, +w_s/2 - 8, theta),
            };
            p.shapes.push_back(Poly(door, kDoor, kFrameDk, 1));
        }

        // ── Side-wall windows along each stall body ──
        for (double theta : { -12.0, 0.0, 12.0 }) {
            for (int i = 0; i < 3; ++i) {
                double bx = R_front + 60 + i * 80;
                if (bx > R_back - 30) break;
                // Window on +y side wall
                Vec2 a = toLocal(bx - 10, +w_s/2 - 2, theta);
                Vec2 b = toLocal(bx + 10, +w_s/2 - 2, theta);
                Vec2 c = toLocal(bx + 10, +w_s/2 - 6, theta);
                Vec2 d = toLocal(bx - 10, +w_s/2 - 6, theta);
                p.shapes.push_back(Poly({a,b,c,d}, kWindow, kFrameDk, 1));
                // Window on -y side wall
                Vec2 e = toLocal(bx - 10, -w_s/2 + 2, theta);
                Vec2 f = toLocal(bx + 10, -w_s/2 + 2, theta);
                Vec2 g = toLocal(bx + 10, -w_s/2 + 6, theta);
                Vec2 h = toLocal(bx - 10, -w_s/2 + 6, theta);
                p.shapes.push_back(Poly({e,f,g,h}, kWindow, kFrameDk, 1));
            }
        }

        // ── Back wall window per stall ──
        for (double theta : { -12.0, 0.0, 12.0 }) {
            Vec2 a = toLocal(R_back - 8, +30, theta);
            Vec2 b = toLocal(R_back - 8, -30, theta);
            Vec2 c = toLocal(R_back - 4, -30, theta);
            Vec2 d = toLocal(R_back - 4, +30, theta);
            p.shapes.push_back(Poly({a,b,c,d}, kWindow, kFrameDk, 1));
        }

        // ── Roof ventilators (small rects near back of each stall) ──
        for (double theta : { -12.0, 0.0, 12.0 }) {
            Vec2 a = toLocal(R_front + 220, +18, theta);
            Vec2 b = toLocal(R_front + 220, -18, theta);
            Vec2 c = toLocal(R_front + 280, -18, theta);
            Vec2 d = toLocal(R_front + 280, +18, theta);
            p.shapes.push_back(Poly({a,b,c,d}, kFrameDk, kFrameDk, 1));
        }

        Add(p);
    }


    // Water tower — 50 × 50mm
    {
        SceneryItem p;
        p.id = "water_tower"; p.displayName = "Water tower";
        p.category = SceneryCategory::Special;
        p.widthMm = 50; p.heightMm = 50;
        // Legs (4 corners)
        p.shapes.push_back(Rect( 5, 25, 6, 25, kBuildingDark, kOutlineSoft));
        p.shapes.push_back(Rect(39, 25, 6, 25, kBuildingDark, kOutlineSoft));
        p.shapes.push_back(Rect(14, 30, 5, 20, kBuildingDark, kOutlineSoft));
        p.shapes.push_back(Rect(31, 30, 5, 20, kBuildingDark, kOutlineSoft));
        // Tank (ellipse top)
        p.shapes.push_back(Ellipse(5, 10, 40, 30, kSteelBlue, kOutlineSoft));
        // Tank dome top
        p.shapes.push_back(Poly({{5,22},{45,22},{42,12},{8,12}}, kBuildingRoof, kOutlineSoft));
        // Spout
        p.shapes.push_back(Rect(22, 35, 6, 12, kBuildingDark, kOutlineSoft));
        Add(p);
    }

    // Coaling tower — 80 × 80mm
    {
        SceneryItem p;
        p.id = "coaling_tower"; p.displayName = "Coaling tower";
        p.category = SceneryCategory::Special;
        p.widthMm = 80; p.heightMm = 80;
        // Base ramp
        p.shapes.push_back(Poly({{0,80},{80,80},{80,40},{40,10},{0,40}},
                                 kConcreteGray, kOutlineSoft));
        // Hopper
        p.shapes.push_back(Poly({{25,10},{55,10},{65,35},{15,35}},
                                 C(80,80,85), kOutlineSoft));
        // Support legs
        p.shapes.push_back(Rect( 5, 40, 8, 40, kBuildingDark, kOutlineSoft));
        p.shapes.push_back(Rect(67, 40, 8, 40, kBuildingDark, kOutlineSoft));
        p.shapes.push_back(Rect(20, 35, 8, 45, kBuildingDark, kOutlineSoft));
        p.shapes.push_back(Rect(52, 35, 8, 45, kBuildingDark, kOutlineSoft));
        Add(p);
    }

    // ── NATURE / TREES ────────────────────────────────────────────────────────

    // Deciduous tree small — Ø34mm
    {
        SceneryItem p;
        p.id = "tree_sm_dec"; p.displayName = "Deciduous tree (small)";
        p.category = SceneryCategory::Tree;
        p.widthMm = 34; p.heightMm = 34;
        // Shadow/ground
        p.shapes.push_back(Ellipse(4, 18, 26, 12, C(100,130,60,120), {0,0,0,0}, 0));
        // Trunk
        p.shapes.push_back(Rect(15, 20, 4, 14, kTrunk, kOutlineSoft));
        // Canopy layers
        p.shapes.push_back(Ellipse(3, 4, 28, 24, kTreeDark, {0,0,0,0}, 0));
        p.shapes.push_back(Ellipse(5, 2, 24, 20, kTreeGreen, {40,80,30,200}));
        // Light highlight
        p.shapes.push_back(Ellipse(8, 3, 12, 10, C(100,170,70,180), {0,0,0,0}, 0));
        Add(p);
    }

    // Deciduous tree large — Ø57mm
    {
        SceneryItem p;
        p.id = "tree_lg_dec"; p.displayName = "Deciduous tree (large)";
        p.category = SceneryCategory::Tree;
        p.widthMm = 57; p.heightMm = 57;
        p.shapes.push_back(Ellipse(6, 32, 45, 18, C(80,110,50,120), {0,0,0,0}, 0));
        p.shapes.push_back(Rect(25, 34, 7, 23, kTrunk, kOutlineSoft));
        p.shapes.push_back(Ellipse(4,  6, 49, 42, kTreeDark, {0,0,0,0}, 0));
        p.shapes.push_back(Ellipse(6,  3, 45, 38, kTreeGreen, {40,80,30,200}));
        p.shapes.push_back(Ellipse(10,  4, 22, 18, C(100,170,70,180), {0,0,0,0}, 0));
        Add(p);
    }

    // Conifer tree — 23 × 40mm (tall and narrow)
    {
        SceneryItem p;
        p.id = "tree_conifer"; p.displayName = "Conifer tree";
        p.category = SceneryCategory::Tree;
        p.widthMm = 23; p.heightMm = 40;
        p.shapes.push_back(Ellipse(4, 32, 15, 6, C(60,90,40,120), {0,0,0,0}, 0));
        p.shapes.push_back(Rect(10, 28, 3, 12, kTrunk, kOutlineSoft));
        // Tiered triangles
        p.shapes.push_back(Poly({{11,0},{23,35},{0,35}}, kTreeDark, {0,0,0,0}, 0));
        p.shapes.push_back(Poly({{11,0},{21,30},{1,30}}, kTreeConifer, {40,80,30,200}));
        p.shapes.push_back(Poly({{11,5},{19,27},{3,27}}, C(50,110,45), {0,0,0,0}, 0));
        p.shapes.push_back(Poly({{11,5},{18,24},{4,24}}, C(70,140,55,200), {0,0,0,0}, 0));
        Add(p);
    }

    // Tree group — 115 × 80mm (mixed)
    {
        SceneryItem p;
        p.id = "tree_group"; p.displayName = "Tree group (3 mixed)";
        p.category = SceneryCategory::Tree;
        p.widthMm = 115; p.heightMm = 80;
        // Ground grass base
        p.shapes.push_back(Ellipse(10, 50, 95, 25, C(80,130,50,150), {0,0,0,0}, 0));
        // Trunks
        p.shapes.push_back(Rect(22, 50, 4, 25, kTrunk, kOutlineSoft));
        p.shapes.push_back(Rect(55, 48, 5, 28, kTrunk, kOutlineSoft));
        p.shapes.push_back(Rect(87, 52, 4, 22, kTrunk, kOutlineSoft));
        // Canopies (back to front)
        p.shapes.push_back(Ellipse(62, 18, 42, 36, kTreeDark, {0,0,0,0}, 0));
        p.shapes.push_back(Ellipse(64, 15, 38, 32, kTreeGreen, {40,80,30,200}));
        p.shapes.push_back(Ellipse(0, 22, 48, 40, C(40,95,35), {0,0,0,0}, 0));
        p.shapes.push_back(Ellipse(2, 20, 44, 36, C(70,140,55), {40,80,30,200}));
        p.shapes.push_back(Ellipse(67, 28, 38, 30, kTreeDark, {0,0,0,0}, 0));
        p.shapes.push_back(Ellipse(69, 26, 34, 27, C(80,150,60), {40,80,30,200}));
        Add(p);
    }

    // Bush / shrub — 23 × 17mm
    {
        SceneryItem p;
        p.id = "bush"; p.displayName = "Bush / shrub";
        p.category = SceneryCategory::Tree;
        p.widthMm = 23; p.heightMm = 17;
        p.shapes.push_back(Ellipse(0, 8, 23, 9, C(60,100,40,150), {0,0,0,0}, 0));
        p.shapes.push_back(Ellipse(2, 2, 10, 12, kTreeGreen, {40,80,30,200}));
        p.shapes.push_back(Ellipse(10, 0, 12, 14, kTreeDark, {0,0,0,0}, 0));
        p.shapes.push_back(Ellipse(11, 1, 11, 12, C(80,150,55), {40,80,30,200}));
        Add(p);
    }

    // ── VEHICLES ──────────────────────────────────────────────────────────────

    // Car — 50 × 20mm (HO scale car)
    {
        SceneryItem p;
        p.id = "car"; p.displayName = "Car";
        p.category = SceneryCategory::Vehicle;
        p.widthMm = 50; p.heightMm = 20;
        // Body
        p.shapes.push_back(Rect(0, 5, 50, 15, kCarRed, kOutlineSoft));
        // Cabin
        p.shapes.push_back(Poly({{10,5},{40,5},{37,0},{13,0}}, C(180,40,30), kOutlineSoft));
        // Windows
        p.shapes.push_back(Rect(13, 1, 10, 4, kCarWindow, kOutlineSoft));
        p.shapes.push_back(Rect(27, 1, 10, 4, kCarWindow, kOutlineSoft));
        // Wheels
        p.shapes.push_back(Ellipse( 6, 16, 9, 9, C(30,30,30), kOutlineSoft));
        p.shapes.push_back(Ellipse(35, 16, 9, 9, C(30,30,30), kOutlineSoft));
        p.shapes.push_back(Ellipse( 8, 17, 5, 5, kCarGray, {0,0,0,0}, 0));
        p.shapes.push_back(Ellipse(37, 17, 5, 5, kCarGray, {0,0,0,0}, 0));
        // Lights
        p.shapes.push_back(Rect( 0, 9, 4, 5, C(255,240,150), {0,0,0,0}, 0));
        p.shapes.push_back(Rect(46, 9, 4, 5, C(220,60,60),   {0,0,0,0}, 0));
        Add(p);
    }

    // Truck — 80 × 25mm
    {
        SceneryItem p;
        p.id = "truck"; p.displayName = "Truck";
        p.category = SceneryCategory::Vehicle;
        p.widthMm = 80; p.heightMm = 25;
        // Trailer
        p.shapes.push_back(Rect(20, 3, 60, 17, kConcreteGray, kOutlineSoft));
        // Cab
        p.shapes.push_back(Rect(0, 5, 22, 15, C(80,100,140), kOutlineSoft));
        p.shapes.push_back(Poly({{0,5},{22,5},{22,0},{5,0}}, kSteelBlue, kOutlineSoft));
        // Cab window
        p.shapes.push_back(Rect(5, 1, 14, 4, kCarWindow, kOutlineSoft));
        // Wheels (4)
        p.shapes.push_back(Ellipse( 5, 17, 9, 9, C(30,30,30), kOutlineSoft));
        p.shapes.push_back(Ellipse(35, 17, 9, 9, C(30,30,30), kOutlineSoft));
        p.shapes.push_back(Ellipse(55, 17, 9, 9, C(30,30,30), kOutlineSoft));
        p.shapes.push_back(Ellipse( 7, 18, 5, 5, kCarGray, {0,0,0,0}, 0));
        p.shapes.push_back(Ellipse(37, 18, 5, 5, kCarGray, {0,0,0,0}, 0));
        p.shapes.push_back(Ellipse(57, 18, 5, 5, kCarGray, {0,0,0,0}, 0));
        Add(p);
    }

    // ── MISC ──────────────────────────────────────────────────────────────────

    // Parking lot — 250 × 120mm (10 spaces)
    {
        SceneryItem p;
        p.id = "parking_lot"; p.displayName = "Parking lot (10 spaces)";
        p.category = SceneryCategory::Special;
        p.widthMm = 250; p.heightMm = 120;
        // Surface
        p.shapes.push_back(Rect(0, 0, 250, 120, C(90,90,90), kOutlineSoft));
        // Access lane
        p.shapes.push_back(Rect(0, 50, 250, 20, C(80,80,80), {0,0,0,0}, 0));
        // Parking bays (top row)
        for (int i = 0; i < 10; i++) {
            double x = i * 25.0;
            // Bay outline
            p.shapes.push_back(OutRect(x+1, 1, 23, 48, kRoadWhite, 1));
        }
        // Parking bays (bottom row)
        for (int i = 0; i < 10; i++) {
            double x = i * 25.0;
            p.shapes.push_back(OutRect(x+1, 71, 23, 48, kRoadWhite, 1));
        }
        // Disabled space markers
        p.shapes.push_back(Rect(2, 2, 22, 46, C(30,80,180,80), {0,0,0,0}, 0));
        p.shapes.push_back(Rect(2, 72, 22, 46, C(30,80,180,80), {0,0,0,0}, 0));
        Add(p);
    }

    // Pedestrian zone — 115 × 57mm
    {
        SceneryItem p;
        p.id = "ped_zone"; p.displayName = "Pedestrian zone";
        p.category = SceneryCategory::Special;
        p.widthMm = 115; p.heightMm = 57;
        // Paving (warm grey)
        p.shapes.push_back(Rect(0, 0, 115, 57, C(195,185,170), kOutlineSoft));
        // Paving pattern (brick-like grid)
        for (double x = 0; x < 115; x += 11.5)
            p.shapes.push_back(Line(x, 0, x, 57, {160,150,138,150}, 1));
        for (double y = 0; y < 57; y += 11.5)
            p.shapes.push_back(Line(0, y, 115, y, {160,150,138,150}, 1));
        // Some benches
        p.shapes.push_back(Rect(10, 20, 20, 6, kWoodBrown, kOutlineSoft));
        p.shapes.push_back(Rect(85, 20, 20, 6, kWoodBrown, kOutlineSoft));
        // Trees (small)
        p.shapes.push_back(Ellipse(52, 18, 12, 12, kTreeGreen, {0,0,0,0}, 0));
        p.shapes.push_back(Rect(57,  26, 3,  8, kTrunk, {0,0,0,0}, 0));
        Add(p);
    }

    // ── BRIDGES & TUNNELS ─────────────────────────────────────────────────────

    // Stone arch bridge moved to track catalog as piece 74636

    // Steel truss bridge — 188 × 80mm
    {
        SceneryItem p;
        p.id = "bridge_truss"; p.displayName = "Steel truss bridge (1 track)";
        p.category = SceneryCategory::Special;
        p.widthMm = 188; p.heightMm = 80;
        // Deck
        p.shapes.push_back(Rect(0, 28, 188, 24, C(100,105,110), kOutlineSoft));
        // Top chord (horizontal beam)
        p.shapes.push_back(Rect(0,  8, 188,  6, C( 80, 85, 90), kOutlineSoft));
        p.shapes.push_back(Rect(0, 66, 188,  6, C( 80, 85, 90), kOutlineSoft));
        // Vertical members
        for (double x = 0; x <= 188; x += 47) {
            p.shapes.push_back(Rect(x,  8, 4, 64, C(80,85,90), kOutlineSoft));
        }
        // Diagonal members (X-bracing)
        for (int i = 0; i < 4; i++) {
            double x0 = i * 47.0, x1 = (i+1) * 47.0;
            p.shapes.push_back(Line(x0+2, 12, x1+2, 68, C(70,75,80,200), 1));
            p.shapes.push_back(Line(x0+2, 68, x1+2, 12, C(70,75,80,200), 1));
        }
        // End portals
        p.shapes.push_back(Rect(  0, 8, 6, 64, C(70,75,80), kOutlineSoft));
        p.shapes.push_back(Rect(182, 8, 6, 64, C(70,75,80), kOutlineSoft));
        Add(p);
    }

    // Concrete viaduct pier — 40 × 60mm (place under elevated track)
    {
        SceneryItem p;
        p.id = "viaduct_pier"; p.displayName = "Viaduct pier (concrete)";
        p.category = SceneryCategory::Special;
        p.widthMm = 40; p.heightMm = 60;
        // Base footing
        p.shapes.push_back(Rect(0, 48, 40, 12, C(175,168,160), kOutlineSoft));
        // Shaft (tapered slightly)
        p.shapes.push_back(Poly({{4,48},{36,48},{32,0},{8,0}}, C(185,178,170), kOutlineSoft));
        // Cap beam
        p.shapes.push_back(Rect(0, 0, 40, 8, C(165,158,150), kOutlineSoft));
        // Surface texture
        p.shapes.push_back(Line(20, 0, 20, 48, C(160,155,148,150), 1));
        Add(p);
    }

    // Tunnel portal (single track) -- horseshoe arch in plan view.
    // Footprint: 40 (X, depth perpendicular to track) x 80 (Y, width across
    // track). At heading=0 the arch opens to +X, so a train running along +X
    // exits the portal in that direction. Track passes through the opening
    // along the Y=40 centerline.
    {
        SceneryItem p;
        p.id = "tunnel_portal_single"; p.displayName = "Tunnel portal (1 track)";
        p.category = SceneryCategory::Tunnel;
        const double W = 40.0;   // depth perpendicular to track
        const double D = 80.0;   // width across track
        p.widthMm = W; p.heightMm = D;

        SceneryColor stoneA = C(140, 130, 118);
        SceneryColor stoneB = C(95,  85,  75);
        SceneryColor outline= C(40,  35,  30);

        const double outerR = D * 0.5;       // 40 mm radius
        const double innerR = outerR * 0.65; // ~26 mm radius -> opening fits track + clearance
        const double cy     = D * 0.5;
        const double openX  = W;             // arch opens at X=W (the +X edge)

        // Build the horseshoe outline as a polygon: outer arc from
        // (openX, 0) around through (openX - outerR, cy) to (openX, D),
        // then inner arc back from (openX, D - (outerR-innerR))
        // through (openX - innerR, cy) to (openX, outerR - innerR).
        // Approximate with N segments.
        const int N = 18;
        std::vector<Vec2> outline_pts;
        for (int i = 0; i <= N; ++i) {
            double t = (double)i / N;
            // Start at top of opening, sweep to bottom (theta from -90 deg
            // through 180 deg to +90 deg, i.e., across the left half plane).
            double theta = M_PI * 0.5 + t * M_PI;   // pi/2 -> 3pi/2
            outline_pts.push_back({ openX + outerR * std::cos(theta),
                                    cy    + outerR * std::sin(theta) });
        }
        for (int i = 0; i <= N; ++i) {
            double t = (double)i / N;
            double theta = M_PI * 1.5 - t * M_PI;   // 3pi/2 -> pi/2
            outline_pts.push_back({ openX + innerR * std::cos(theta),
                                    cy    + innerR * std::sin(theta) });
        }
        // Filled main shape (lighter masonry)
        p.shapes.push_back(Poly(outline_pts, stoneA, outline));

        // Alternating darker blocks along the arch for coursing -- draw
        // small wedges between adjacent outer-to-inner radial cuts.
        // Make every other wedge the darker stone.
        const int blocks = 12;
        for (int b = 0; b < blocks; ++b) {
            if ((b & 1) == 0) continue;     // skip the lighter wedges
            double t0 = (double)b / blocks;
            double t1 = (double)(b+1) / blocks;
            double th0 = M_PI * 0.5 + t0 * M_PI;
            double th1 = M_PI * 0.5 + t1 * M_PI;
            std::vector<Vec2> wedge = {
                { openX + outerR * std::cos(th0), cy + outerR * std::sin(th0) },
                { openX + outerR * std::cos(th1), cy + outerR * std::sin(th1) },
                { openX + innerR * std::cos(th1), cy + innerR * std::sin(th1) },
                { openX + innerR * std::cos(th0), cy + innerR * std::sin(th0) }
            };
            p.shapes.push_back(Poly(wedge, stoneB, stoneB));
        }
        Add(p);
    }

    // Retaining wall — 188 × 30mm (fits one straight track length)
    {
        SceneryItem p;
        p.id = "retaining_wall"; p.displayName = "Retaining wall 188mm";
        p.category = SceneryCategory::Special;
        p.widthMm = 188; p.heightMm = 30;
        p.shapes.push_back(Rect(0, 0, 188, 30, C(145,138,130), kOutlineSoft));
        // Horizontal courses
        for (double y = 8; y < 30; y += 8)
            p.shapes.push_back(Line(0, y, 188, y, C(120,114,107,200), 1));
        // Vertical joints (offset per course)
        for (double x = 20; x < 188; x += 40)
            p.shapes.push_back(Line(x,  0, x,  8, C(120,114,107,200), 1));
        for (double x = 40; x < 188; x += 40)
            p.shapes.push_back(Line(x,  8, x, 16, C(120,114,107,200), 1));
        for (double x = 20; x < 188; x += 40)
            p.shapes.push_back(Line(x, 16, x, 24, C(120,114,107,200), 1));
        for (double x = 40; x < 188; x += 40)
            p.shapes.push_back(Line(x, 24, x, 30, C(120,114,107,200), 1));
        Add(p);
    }

    m_loaded = true;
}

// ── Lookup ────────────────────────────────────────────────────────────────────
const SceneryItem* SceneryCatalog::Find(const std::string& id) const {
    auto it = m_index.find(id);
    return (it != m_index.end()) ? &m_items[it->second] : nullptr;
}

} // namespace MC
