#pragma once
#include <vector>
#include <optional>
#include "geometry/Transform2D.h"
#include "geometry/ConnectionPoint.h"
#include "model/PlacedPiece.h"
#include "model/TrackPiece.h"

namespace MC {

// ── Result of a snap query ────────────────────────────────────────────────────
struct SnapResult {
    bool        found        = false;
    int         targetPieceId  = -1;  // existing piece on canvas
    int         targetConnIdx  = -1;  // its open connection index
    int         sourceConnIdx  = -1;  // which connection on the dragged piece snaps
    Transform2D snappedTransform;     // where to place the dragged piece
    double      distanceMm   = 0.0;  // distance before snap (for UI feedback)
};

// ── SnapEngine ────────────────────────────────────────────────────────────────
class SnapEngine {
public:
    // Snap radius in mm — within this distance an endpoint will auto-snap
    static constexpr double kSnapRadiusMm = 25.0;

    // Given a piece being dragged (with its current free-hand transform),
    // search all placed pieces on the canvas for the nearest compatible
    // open endpoint and compute the exact snapped transform.
    //
    //   dragged         — the catalog definition of the piece being placed
    //   draggedTransform— current (free-hand) world transform of dragged piece
    //   canvas          — all already-placed pieces
    //   catalog         — used to resolve piece definitions for canvas pieces
    //
    // Returns SnapResult::found == false if nothing is within snap radius.
    static SnapResult FindSnap(
        const TrackPiece&               dragged,
        const Transform2D&              draggedTransform,
        const std::vector<PlacedPiece>& canvas,
        const class CatalogLoader&      catalog);

    // Compute the exact world transform that places 'piece' so that
    // its connection[sourceIdx] perfectly meets target world-space endpoint.
    static Transform2D ComputeSnapTransform(
        const TrackPiece&   piece,
        int                 sourceConnIdx,
        const ConnectionPoint& targetWorldConn);

    // Compute all world-space open connection points for a placed piece
    static std::vector<ConnectionPoint> WorldConnections(
        const PlacedPiece&  placed,
        const TrackPiece&   def);

    // Check if two series are allowed to connect
    static bool SeriesCompatible(TrackSeries a, TrackSeries b) {
        // Adapter piece connSeries overrides this — checked separately
        if (a == b) return true;
        // MarlinC ↔ MarlinK: NOT compatible (except via adapter 24922)
        // TrixC ↔ anything else: NOT compatible
        return false;
    }

    // Like FindSnap but IGNORES series constraints — used to detect
    // when a drop was geometrically close but series-incompatible.
    // Returns the series pair that was attempted, or empty if no near miss.
    struct IncompatibleSnapResult {
        bool        found       = false;
        TrackSeries dragSeries  = TrackSeries::MarlinC;
        TrackSeries targSeries  = TrackSeries::MarlinC;
    };
    static IncompatibleSnapResult FindIncompatibleSnap(
        const TrackPiece&               dragged,
        const Transform2D&              draggedTransform,
        const std::vector<PlacedPiece>& canvas,
        const class CatalogLoader&      catalog);

private:
    static bool AreCompatible(
        const ConnectionPoint& a,
        const ConnectionPoint& b,
        double snapRadiusMm = kSnapRadiusMm);
};

} // namespace MC
