#pragma once
#include <wx/wx.h>
#include <wx/glcanvas.h>
#include "canvas/CanvasState.h"
#include "io/CatalogLoader.h"
#include <vector>
#include <string>
#include <unordered_map>

// ── Basic types ───────────────────────────────────────────────────────────────
struct V3     { float x, y, z; };
struct Color3 { float r, g, b; };
struct Color4 { float r, g, b, a; };

struct Mesh {
    std::vector<V3>          verts;
    std::vector<V3>          normals;
    std::vector<Color3>      colors;
    std::vector<unsigned int> indices;
    void clear() { verts.clear(); normals.clear(); colors.clear(); indices.clear(); }
};

// Geometry that must be alpha-blended. Kept apart from the opaque meshes
// because transparent surfaces have to be drawn LAST, after everything solid,
// with depth writes off -- otherwise they occlude whatever is behind them and
// you see nothing through them.
struct MeshRGBA {
    std::vector<V3>           verts;
    std::vector<V3>           normals;
    std::vector<Color4>       colors;
    std::vector<unsigned int> indices;
    void clear() { verts.clear(); normals.clear(); colors.clear(); indices.clear(); }
};

// ── Per-catalog-piece geometry (built once, reused for every instance) ────────
struct PieceMesh {
    Mesh mesh;
};

// ── Per-placed-piece transform ────────────────────────────────────────────────
struct InstanceTransform {
    std::string catalogNumber;
    float wx, wy;
    float wz           = 0.0f;
    float headingDeg;
    float gradeDeg     = 0.0f;
    float entryDirDeg  = 0.0f;
    float lengthMm     = 0.0f;
    bool  flipped      = false;  // connected via exit (conn[1]) = piece runs backwards
};

// ── Per-placed-scenery transform ──────────────────────────────────────────────
struct SceneryInstance {
    std::string itemId;
    float wx, wy;
    float wz = 0.0f;       // elevation from the item's level height
    float headingDeg;
    float halfW = 0.0f;
    float halfH = 0.0f;
    float scaleX = 1.0f;
    float scaleY = 1.0f;
};

// ── GL Canvas ─────────────────────────────────────────────────────────────────
class TrackGLCanvas : public wxGLCanvas {
public:
    TrackGLCanvas(wxWindow* parent, const MC::CanvasState& state);
    void RebuildMesh(const MC::CanvasState& state);

private:
    void OnPaint(wxPaintEvent&);
    void OnSize(wxSizeEvent&);
    void OnMouseMove(wxMouseEvent&);
    void OnMouseDown(wxMouseEvent&);
    void OnMouseUp(wxMouseEvent&);
    void OnMouseWheel(wxMouseEvent&);
    void OnKeyDown(wxKeyEvent&);

    void InitGL();
    void SetupCamera();
    void DrawGrid();
    void DrawBaseboard();
    void RebuildBaseboardMesh(const MC::CanvasState& state);
    void DrawTunnels();
    void RebuildTunnelMesh(const MC::CanvasState& state);
    void DrawInstance(const InstanceTransform& inst);
    void DrawSceneryInstance(const SceneryInstance& inst);

    wxGLContext* m_ctx    = nullptr;
    bool         m_glInit = false;

    // Instanced rendering data
    std::unordered_map<std::string, PieceMesh>    m_pieceMeshCache;
    std::vector<InstanceTransform>                m_instances;
    Mesh                                          m_supportMesh;
    Mesh                                          m_baseboardMesh;
    Mesh                                          m_tunnelMesh;
    // Wireframe outlines for shapes that contain tunnel track. Each entry
    // is one closed polygon (top loop + bottom loop + vertical risers) drawn
    // as line segments — see the shape build loop in RebuildMesh. Pairs of
    // verts are line endpoints; color is per-shape.
    struct WireSegment { float ax,ay,az,bx,by,bz, r,g,b; };
    std::vector<WireSegment>                      m_shapeWires;
    // Polygon shapes are split by opacity, because a solid shape and a
    // see-through one need opposite depth settings and cannot share a pass:
    //
    //   m_shapeMeshOpaque - alpha >= ~100%. Drawn with the solid geometry,
    //       depth writes ON, so the shape's own faces occlude each other and
    //       it renders as a proper closed box with a roof.
    //
    //   m_shapeMesh - alpha < 100%. Drawn last with blending on and depth
    //       writes OFF, so you can see through it (and so two overlapping
    //       transparent shapes don't punch holes in one another).
    //
    // Putting an opaque shape in the blended pass is what loses its roof: with
    // depth writes off, its far walls paint straight over its own top cap.
    MeshRGBA                                      m_shapeMesh;
    MeshRGBA                                      m_shapeMeshOpaque;
    std::unordered_map<std::string, PieceMesh>    m_sceneryMeshCache;
    std::vector<SceneryInstance>                  m_sceneryInstances;

    // Camera
    float m_rotX =  35.0f;
    float m_rotZ =  30.0f;
    float m_zoom = 800.0f;
    float m_panX =   0.0f;
    float m_panY =   0.0f;
    float m_cx   =   0.0f;
    float m_cy   =   0.0f;

    wxPoint m_lastMouse;
    bool    m_ldown = false;
    bool    m_rdown = false;

    wxDECLARE_EVENT_TABLE();
};

// ── Frame window ──────────────────────────────────────────────────────────────
class View3DFrame : public wxFrame {
public:
    View3DFrame(wxWindow* parent, const MC::CanvasState& state);
    void RefreshLayout(const MC::CanvasState& state);

private:
    TrackGLCanvas* m_canvas = nullptr;
};


