#pragma once
#include <wx/wx.h>
#include <wx/panel.h>
#include "canvas/CanvasState.h"
#include "canvas/Renderer.h"
#include "geometry/SnapEngine.h"

class MainFrame;
namespace MC { struct SceneryItem; }

class CanvasPanel : public wxPanel {
public:
    CanvasPanel(wxWindow* parent, MainFrame* frame);

    void ZoomBy(double factor);
    void ZoomFit(bool contentOnly = false);
    void SetZoomForZ();
    void ResetView();
    void SetShowGrid(bool show);
    void BeginPieceDrag(const std::string& catalogNumber);
    void BeginSceneryDrag(const std::string& itemId);
    void BeginHelixDrag(const std::string& helixVariant);
    void PlaceAndJoinToSelected(const std::string& catalogNumber);
    MC::Vec2 ScreenToWorld(wxPoint pt) const;

    // Elevation wizard: enter "pick anchor" mode.
    // method: 1 = Set Grade, 2 = Set Target Height.
    void BeginElevationPick(int method);

private:
    void OnPaint(wxPaintEvent&);
    void OnSize(wxSizeEvent&);
    void OnMouseMove(wxMouseEvent&);
    void OnLeftDblClick(wxMouseEvent&);
    void OnLeftDown(wxMouseEvent&);
    void OnLeftUp(wxMouseEvent&);
    void OnRightDown(wxMouseEvent&);
    void OnRightUp(wxMouseEvent&);
    void OnMiddleDown(wxMouseEvent&);
    void OnMiddleUp(wxMouseEvent&);
    void OnMouseWheel(wxMouseEvent&);
    // ── Autofill ─────────────────────────────────────────────────────────
    // A candidate fill for the gap between the two selected pieces.
    struct Suggestion {
        std::vector<std::string> pieces;
        double totalLen = 0.0;   // mm for straight fills, degrees for curved
        // Curved fills only: true if each piece must be placed end-for-end
        // (snapped by conn[1] not conn[0]) so it bends the way the solved arc
        // requires. A catalog curve has one fixed bend direction.
        bool reversed = false;
    };

    // Everything the solver works out about a gap.
    struct AutofillResult {
        std::vector<Suggestion> suggestions;
        MC::ConnectionPoint     bestA, bestB;   // the two free connectors
        double                  bestDist   = 0.0;
        bool                    isStraight = false;
        bool                    mixedSeries = false;  // the two pieces differ in series
    };

    // The single source of truth for "can this gap be filled, and how".
    // CanAutofill() and DoAutofill() BOTH call this, so the context-menu item
    // can never appear for a gap that Autofill would then refuse to fill.
    bool FindAutofillSuggestions(AutofillResult& out) const;

    bool CanAutofill() const;   // true iff FindAutofillSuggestions() succeeds
    void DoAutofill();

    // ── Elevation wizard canvas interaction ───────────────────────────────
    void DoElevationGrade(int pieceId, int connIdx);
    void DoElevationTarget(int pieceId, int connIdx);
    void PlaceElevatedRun(int anchorId, int anchorConnIdx,
                          const MC::ConnectionPoint& anchorConn,
                          double distance, double targetHeightMm);

    void DoCopy();
    void DoPaste(wxPoint screenPt);
    void OnKeyDown(wxKeyEvent&);
    void OnMouseLeave(wxMouseEvent&);

    void DrawRulers(wxDC& dc);
    int  HitTest(const wxPoint& screenPt) const;
    int  SceneryHitTest(const wxPoint& screenPt) const;
    // Test whether the click hits the perceived R1/R2 rings of any helix.
    // Returns the helix id, or -1 if no helix is hit.
    int  HelixHitTest(const wxPoint& screenPt) const;

    // Platform resize handles. Active only when the current selection is
    // exactly one scenery item whose catalog category == Platform.
    // Returns -1 if no platform is currently eligible.
    int  GetResizablePlatformId() const;
    // Returns 0..7 handle index hit, or -1.
    int  HitTestPlatformHandle(const wxPoint& screenPt, int platformId) const;
    // Compute world position of handle `idx` (0..7) on the given placed item.
    MC::Vec2 PlatformHandleWorldPos(const MC::PlacedScenery& s,
                                     const MC::SceneryItem& def, int idx) const;
    // Index of the handle diagonally opposite (corner) or across (edge).
    static int OppositeHandle(int idx);
    void DrawPlatformHandles(wxDC& dc, int platformId);
    void UpdateDragSnap(const wxPoint& screenPt);
    void CommitDrag(const wxPoint& screenPt);
    void CommitSceneryDrag();
    void CancelDrag();
    void ApplyGradeToNewPiece(MC::PlacedPiece& newPiece, int anchorId, int anchorConnIdx, int newPieceConnIdx = 0);
    void RotateSelection(bool clockwise = true);
    void ShowSceneryResizeDialog(int sceneryId);
    void OnRotSnapTimer(wxTimerEvent&);
    void UpdateStatusBar(const MC::Vec2& worldPos);

    MainFrame*        m_frame    = nullptr;
    MC::Renderer      m_renderer;
    MC::ViewTransform m_view;

    // Piece drag from palette
    bool              m_draggingPiece  = false;
    std::string       m_dragCatalogNum;
    MC::Transform2D   m_dragTransform;
    MC::SnapResult    m_currentSnap;

    // Scenery drag from palette
    bool              m_draggingScenery = false;
    std::string       m_dragSceneryId;
    MC::Transform2D   m_dragSceneryTransform;

    // Helix drag from palette
    bool              m_draggingHelix    = false;
    std::string       m_dragHelixVariant;
    MC::Vec2          m_dragHelixPos{0, 0};

    // Pan
    bool              m_panning         = false;
    bool              m_spaceWasPanning = false;
    bool              m_justCloned      = false;
    wxPoint           m_panStart;
    double            m_panStartX  = 0, m_panStartY = 0;

    // Move selected
    bool              m_movingSelection = false;
    bool              m_moveArmed       = false;   // waiting for threshold before moving
    wxPoint           m_moveArmScreen;
    bool              m_draggingLabel   = false;
    int               m_dragLabelId     = -1;
    MC::Vec2          m_dragLabelOffset;
    MC::Vec2          m_moveStartWorld;
    std::vector<MC::Transform2D> m_moveOrigTransforms;
    // Original centers of any helices being moved (helixId -> center).
    std::vector<std::pair<int, MC::Vec2>> m_moveOrigHelixCenters;
    MC::SnapResult           m_moveSnap;
    int                      m_moveSnapPieceId = -1;
    std::vector<MC::PlacedPiece>   m_clipboard;          // copied track pieces
    std::vector<MC::PlacedScenery> m_clipboardScenery;   // copied scenery items
    MC::CanvasShape                m_clipboardShape;      // copied polygon shape
    bool                           m_clipboardShapeValid = false;

    // Rubber band
    bool              m_rubberBanding = false;
    wxPoint           m_rubberStart;
    wxPoint           m_rubberEnd;

    // Deck dragging (moving a level's shape outline; track/scenery stay put)
    bool              m_draggingDeck   = false;
    int               m_dragDeckLevel  = -1;
    MC::Vec2          m_deckDragStartWorld;
    std::vector<MC::Vec2> m_deckOrigShape;
    // Returns the level id whose deck outline is under the screen point, else -1.
    int  DeckHitTest(const wxPoint& screenPt) const;

    // Measuring tape — placement: between the first and second click, the
    // mouse position updates a rubber-banded preview from m_measureA.
    bool              m_measuring    = false;
    MC::Vec2          m_measureA;
    MC::Vec2          m_measureB;

    // Measuring tape — drag (moving an existing measurement on the canvas).
    // What  : 0=none, 1=line (translates whole measurement),
    //         2=label (offsets only the label).
    int               m_measureDragWhat = 0;
    int               m_measureDragId   = -1;
    MC::Vec2          m_measureDragStartWorld;
    MC::Vec2          m_measureOrigA;
    MC::Vec2          m_measureOrigB;
    MC::Vec2          m_measureOrigLabelOffset;

    // Hit-tests for an existing measurement. Returns the measurement id under
    // the cursor and (via *what) which part — 1=line, 2=label. -1 if none.
    int  MeasurementHitTest(const wxPoint& screenPt, int* what) const;

    // Free-form polygon tool — vertex placement state. The vertices are
    // accumulated in world mm; the cursor world position is rubber-banded
    // from the last vertex (and back to the first when near it, to preview
    // closure).
    bool              m_drawingPolygon  = false;
    std::vector<MC::Vec2> m_polygonVerts;
    MC::Vec2          m_polygonCursorWorld;
    bool              m_polygonCloseHover = false;   // cursor near first vertex

    // Shape drag (moving an existing free-form polygon).
    bool              m_draggingShape   = false;
    int               m_dragShapeId     = -1;
    MC::Vec2          m_shapeDragStartWorld;
    std::vector<MC::Vec2> m_shapeOrigVerts;
    // Returns the shape id under the screen point (inside polygon or near
    // outline). -1 if none.
    int  ShapeHitTest(const wxPoint& screenPt) const;
    // Open a modal editor for a polygon shape (label, color, height, outline).
    void ShowShapeEditorDialog(int shapeId);

    // Free rotation (Alt+drag)
    bool              m_freeRotating    = false;
    wxPoint           m_rotStartScreen;
    MC::Vec2          m_rotPivot;
    double            m_rotStartHeading = 0.0;
    std::vector<MC::Transform2D> m_rotOrigTransforms;
    // Free-rotate needs each shape's vertices as they were when the drag
    // began, so every mouse-move rotates from the original rather than
    // compounding tiny rotations (which would drift and distort the shape).
    std::vector<std::vector<MC::Vec2>> m_rotOrigShapeVerts;
    double            m_rotCurrentDeg   = 0.0;
    bool              m_rotSnapping     = false;  // flashing at cardinal angle
    int               m_rotSnapFlashMs  = 0;
    wxTimer*          m_rotSnapTimer    = nullptr;
    // When the rotation is operating on a single complete helix selection,
    // we pivot around the helix center and also rotate h.heading so the
    // rendered interior curves track the stubs.
    int               m_rotHelixId         = -1;
    double            m_rotOrigHelixHeading = 0.0;

    // Platform resize (drag a handle on a selected single platform scenery item)
    // Handle index layout (in the platform's local frame, scale already applied):
    //   0=NW  1=N   2=NE
    //   7=W         3=E
    //   6=SW  5=S   4=SE
    bool              m_resizingPlatform = false;
    int               m_resizeSceneryId  = -1;
    int               m_resizeHandle     = -1;   // 0..7
    MC::Vec2          m_resizeAnchorWorld;       // world position of the anchor (opposite) handle
    double            m_resizeOrigScaleX = 1.0;
    double            m_resizeOrigScaleY = 1.0;
    MC::Vec2          m_resizeOrigPos;
    double            m_resizeOrigHeading = 0.0;

    // ── Elevation wizard canvas mode ────────────────────────────────────────
    // 0 = off, 1 = picking anchor piece, 2 = picking end point (target method)
    int               m_elevMode        = 0;
    int               m_elevMethod      = 0;    // 1 = grade, 2 = target
    int               m_elevAnchorId    = -1;
    int               m_elevAnchorConn  = -1;   // connector index on anchor
    MC::ConnectionPoint m_elevAnchorPt;          // world connection point
    MC::Vec2          m_elevCursor{0, 0};        // live cursor for rubber-band

    wxDECLARE_EVENT_TABLE();
};
