#pragma once
#include <string>
#include <vector>
#include "geometry/Transform2D.h"
#include "model/TrackPiece.h"

namespace MC {

// ── A TrackPiece placed on the designer canvas ────────────────────────────────
struct PlacedPiece {
    int         id = -1;           // unique canvas ID, assigned on placement
    std::string catalogNumber;     // references TrackPiece in catalog
    Transform2D transform;         // world position (mm) + rotation (degrees)
    bool        selected  = false;
    bool        flipped   = false;

    // Elevation -- height of the ENTRY end above baseboard (mm)
    // Exit Z is derived from z_mm + arc_length * sin(grade_angle)
    // Grade is computed from the connected next piece's z_mm.
    double      z_mm      = 0.0;
    double      gradeDeg  = 0.0;     // chain-direction grade (positive = chain rises)

    // When the snap engine joins this piece "backwards" relative to chain
    // direction (i.e. conn[1] is the chain entry, conn[0] is the chain exit),
    // chainReversed is true. z_mm always refers to conn[0]'s height; the
    // exit-height calculation flips sign when chainReversed is set, and the
    // 3D renderer flips its tilt direction. The user-facing gradeDeg always
    // reflects the chain's direction of travel, so the property panel shows
    // a consistent "+/-" sign regardless of connector orientation.
    bool        chainReversed = false;

    // Optional track color (cosmetic only)
    bool        hasColor  = false;
    uint8_t     colorR    = 0;
    uint8_t     colorG    = 0;
    uint8_t     colorB    = 0;
    int         levelId   = 1;    // which layout level this piece belongs to
    int         helixId   = -1;   // if this piece is part of a helix, the helix's id

    // True when this piece was auto-spawned with a turntable as part of the
    // included 6-pack of spoke extensions (74871). The flag persists across
    // delete/move of the piece and across save/load, so the piece is not
    // counted in the buy-track tally even after the parent turntable has
    // been removed. New pieces the user drags in (e.g. to fill remaining
    // spokes) do NOT get this flag and DO count.
    bool        freeWithTurntable = false;

    // Per-connection: which other PlacedPiece.id + connection index this joins
    // -1 means open/unconnected
    struct Joint {
        int pieceId  = -1;
        int connIdx  = -1;
    };
    std::vector<Joint> joints; // joints.size() == piece.connections.size()

    // Operational properties (written to plan.xml)
    std::string blockId;       // if this piece is inside a block
    int         dccAddress = 0;// turnouts only
    int         dccPort    = 0;
    std::string protocol   = "MM"; // "MM" or "DCC"

    // Compute world-space position of a connection point given the catalog piece
    ConnectionPoint worldConnection(const TrackPiece& def, int idx) const {
        if (idx < 0 || idx >= (int)def.connections.size())
            return {};
        const auto& local = def.connections[idx];
        ConnectionPoint world;
        world.pos     = transform.transformPoint(local.pos);
        world.heading = Transform2D::normalizeAngle(transform.heading + local.heading);
        world.isOpen  = (joints.size() > (size_t)idx) ? (joints[idx].pieceId == -1) : true;
        world.index   = idx;
        return world;
    }
};

} // namespace MC
