#pragma once

#include <map>
#include <memory>
#include <optional>
#include <random>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include <QColor>
#include <QElapsedTimer>
#include <QOpenGLWidget>
#include <QPoint>
#include <QPointF>
#include <QRectF>
#include <QTimer>
#include <QVector2D>

#include "Blueprint.h"
#include "BuildingConfig.h"
#include "BlueprintModeExitedEvent.h"
#include "BlueprintPlacementRequestedEvent.h"
#include "BuilderModeExitedEvent.h"
#include "BuildingType.h"
#include "BuildingTypeSelectedEvent.h"
#include "BuildingId.h"
#include "DeconstructModeChangedEvent.h"
#include "DeconstructModeToggleRequestedEvent.h"
#include "EventHandler.h"
#include "ExitBlueprintModeRequestedEvent.h"
#include "ExitBuilderModeRequestedEvent.h"
#include "DebugDrawToggledEvent.h"
#include "ArtifactCountChangedEvent.h"
#include "BeamFiredEvent.h"
#include "CommandRequestedEvent.h"
#include "SchematicChoiceOption.h"
#include "WinEvent.h"
#include "SpeedChangeRequestedEvent.h"

#include "entt/entity/entity.hpp"
#include "BeltDragPath.h"
#include "CommandManager.h"
#include "EntitySelectionChangedEvent.h"
#include "GameConfig.h"
#include "Rotation.h"
#include "Tick.h"
#include "TickDriver.h"
#include "TunnelCompletion.h"
#include "VisualsConfig.h"

struct Command;
struct ParsedReplay;
class ItemIconCache;
class ReplayPlayer;
class Simulation;
class QPainter;
class QSvgRenderer;

struct QPointCompare
{
    bool operator()(const QPoint& a, const QPoint& b) const
    {
        if (a.x() != b.x()) { return a.x() < b.x(); }
        return a.y() < b.y();
    }
};

// Tunnel entries/exits indexed by their single-cell tile (REQ-BLD-TUNNEL-MODE).
using TunnelTileMap = std::map<QPoint, TunnelTileInfo, QPointCompare>;

class GameWorldView : public QOpenGLWidget,
                      public CombinedEventHandler<BeamFiredEvent,
                                                  BuildingTypeSelectedEvent,
                                                  ExitBuilderModeRequestedEvent,
                                                  DeconstructModeToggleRequestedEvent,
                                                  BlueprintPlacementRequestedEvent,
                                                  ExitBlueprintModeRequestedEvent,
                                                  SpeedChangeRequestedEvent,
                                                  CommandRequestedEvent>
{
    Q_OBJECT

public:
    // itemIcons is the window-wide per-item icon cache (REQ-UI-ITEM-ICON); not
    // owned, must outlive this widget.
    GameWorldView(Simulation* sim, const GameConfig* config,
                  const VisualsConfig* visuals, const std::string& configDir,
                  ItemIconCache* itemIcons, const ParsedReplay* replay,
                  QWidget* parent = nullptr);
    ~GameWorldView() override;

    double getGameSpeed() const;
    bool isDebugDrawEnabled() const;
    void resetFrameTimer();
    void setGameSpeed(double multiplier);
    void resetForNewGame();

protected:
    void initializeGL() override;
    void paintGL() override;
    void keyPressEvent(QKeyEvent* event) override;
    void keyReleaseEvent(QKeyEvent* event) override;
    void mousePressEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* event) override;

private slots:
    void onFrame();

private:
    void handleEvent(std::shared_ptr<const BeamFiredEvent> event) override;
    void handleEvent(std::shared_ptr<const BuildingTypeSelectedEvent> event) override;
    void handleEvent(std::shared_ptr<const ExitBuilderModeRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const DeconstructModeToggleRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const BlueprintPlacementRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const ExitBlueprintModeRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const SpeedChangeRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const CommandRequestedEvent> event) override;

    // Enqueue a sim command onto the CommandManager (the single mutation path).
    void enqueueCommand(std::shared_ptr<const Command> command);

    // Enqueue a plain (unconfigured) building placement.
    void enqueuePlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);

    // True if the player can currently afford to place one building of `type`.
    // Used to pre-validate placements whose UI follow-up depends on success.
    bool canAfford(BuildingType type) const;

    void drawTiles(QPainter& painter);
    void drawPortItems(QPainter& painter);
    void drawBuildings(QPainter& painter);
    void drawSelectionHighlights(QPainter& painter);
    void drawCopyConfigFeedback(QPainter& painter);
    void drawStations(QPainter& painter);
    void drawBeltItems(QPainter& painter);
    // Draws a single item centered at widget-space `center`, spanning `halfPx` in
    // each direction (a half-tile). Uses the item's icon when one exists
    // (REQ-UI-ITEM-ICON), otherwise falls back to the colored square from
    // visuals.toml. Shared by drawBeltItems and drawPortItems.
    void drawWorldItem(QPainter& painter, const std::string& itemId,
                       QPointF center, float halfPx);
    void drawDebris(QPainter& painter);
    void drawShips(QPainter& painter);
    void drawHpBar(QPainter& painter, qreal left, qreal top, qreal width,
                   float fraction, bool isEnemy);
    void drawDebugSensorRanges(QPainter& painter);
    void drawDebugTargetLines(QPainter& painter);
    void drawDebugOverlay(QPainter& painter);
    void drawBeams(QPainter& painter);
    void drawOverlays(QPainter& painter);
    void drawScreenSpace(QPainter& painter);
    // Vignette-style border shown while the game is paused (speed 0x, REQ-UI-PAUSE-BORDER)
    // to make the paused state hard to miss: a black frame whose alpha fades from 50% at
    // the viewport edges to 0% toward the center.
    void drawPauseBorder(QPainter& painter);
    // Vignette-style border shown while deconstruct mode is active (REQ-UI-DECONSTRUCT-BORDER),
    // tinted with the deconstruct overlay color (including its configured alpha) from
    // visuals.toml instead of black.
    void drawDeconstructBorder(QPainter& painter);
    // Shared drawing for the pause/deconstruct vignettes: a mitred picture-frame border
    // whose alpha fades from `edgeColor` (with its own alpha) at the viewport edge to
    // fully transparent toward the center.
    void drawVignetteBorder(QPainter& painter, const QColor& edgeColor);
    void drawReplayOverlay(QPainter& painter);

    float    getTilePx() const;
    float    getViewportWidthTiles() const;
    // World-X (tiles) at the left edge of the viewport. m_scrollXTiles stores the
    // view center; this derives the left edge the world<->widget conversions need.
    float    getViewLeftTiles() const;
    QPointF  worldToWidget(QVector2D worldPos) const;
    QPointF  tileToWidget(QPoint tile) const;
    QPoint   widgetToTile(QPoint widgetPt) const;
    QRectF   tileRect(QPoint tile) const;
    QRect    getViewportRect() const;
    // Widget-space rectangle covering a building or construction site's footprint,
    // or nullopt if the id resolves to neither. Shared by the selection highlight
    // and the copy-settings feedback (REQ-BLD-COPY-CONFIG-FEEDBACK).
    std::optional<QRectF> footprintWidgetRect(BuildingId id) const;

    float    getAsteroidLeftEdge() const;
    float    getEnemyStationRightEdge() const;
    // Horizontal pan speed at a given view-center X, in tiles/s (REQ-UI-SCROLL-SPEED).
    float    panSpeedTilesPerSecondAt(float viewCenterXTiles) const;
    void     clampScroll();

    bool     isValidPlacement(BuildingType type, QPoint anchor, Rotation rot) const;
    std::optional<BuildingId> buildingAtTile(QPoint tile) const;
    std::optional<BuildingId> siteAtTile(QPoint tile) const;
    // Ids of all buildings and construction sites whose footprint intersects
    // the tile box spanned by the two (unordered) corner tiles.
    std::vector<BuildingId> buildingsInBox(QPoint cornerA, QPoint cornerB) const;
    QVector2D  widgetToWorld(QPoint widgetPt) const;

    void     drawPortGlyph(QPainter& painter, QPoint tile,
                           Rotation direction, const QColor& color,
                           bool centered);

    void     drawBuildingGhost(QPainter& painter, BuildingType type,
                               QPoint anchorTile, Rotation rotation, bool valid,
                               bool showPortTargetGlyphs);

    // Loads the per-building world icons (REQ-UI-WORLD-ICON) from
    // <configDir>/../icons/buildings once at construction. Only the building
    // types with a world icon are loaded (production buildings, HQ, stations);
    // belts, splitters, and tunnels are deliberately excluded so their
    // orientation stays readable. The SVG's chip background is stripped; the
    // glyph is pre-rendered in both white and dark ink for auto-contrast.
    void     loadBuildingIcons(const std::string& configDir);
    // Draws a building's world icon glyph centered in box, choosing the white or
    // dark pre-rendered variant by fill luminance so it stays legible. Returns
    // false if the type has no world icon (caller falls back to the text glyph).
    bool     drawBuildingIcon(QPainter& painter, BuildingType type,
                              const QRectF& box, const QColor& fill) const;

    void     placeBlueprintAtTile(QPoint center);

    std::optional<QVector2D> entityPosition(entt::entity entity) const;
    // Clears the debris selection, emitting an empty DebrisSelectionChangedEvent when
    // it was non-empty (REQ-UI-DEBRIS-CLICK-SELECT). Used when another selection
    // category takes over.
    void                     clearDebrisSelection();
    // Drops despawned or fully-collected debris from the selection and re-emits
    // when it changed (REQ-UI-DEBRIS-CLICK-SELECT). Called each frame from onFrame().
    void                     pruneDespawnedDebris();
    // Clears the actor selection, emitting an empty EntitySelectionChangedEvent when it was
    // non-empty (REQ-UI-ENTITY-CLICK-SELECT). Used when buildings take over.
    void                     clearEntitySelection();
    // Drops despawned or dead actors from the selection and re-emits when it changed
    // (REQ-UI-ENTITY-CLICK-SELECT). Called each frame from onFrame().
    void                     pruneDespawnedActors();
    // True if the given actor is part of the current actor selection.
    bool                     isEntitySelected(entt::entity entity) const;
    void                     stepSpeed(int delta);
    void                     placeAtTile(QPoint tile);

    // Unified tunnel build mode (REQ-BLD-TUNNEL-MODE). Active while the builder type
    // is TunnelEntry; the ghost then resolves to an entry or exit by hovered position.
    bool         inTunnelMode() const;
    // The building type the ghost currently represents: the position-resolved tunnel
    // type in tunnel mode, otherwise the plain builder type.
    BuildingType effectiveBuilderType() const;
    // Recomputes m_tunnelGhostType and m_tunnelPartnerTile from the current ghost
    // tile, rotation, and sub-tile cursor position. Only meaningful in tunnel mode.
    void         updateTunnelGhost();
    // Indexes every tunnel entry/exit — built or still a construction site — by its
    // single-cell tile. Shared by the placement preview and the selection highlight.
    TunnelTileMap collectTunnelTiles() const;
    // Wraps a tunnel tile index in the lookup functor the TunnelCompletion helpers
    // take. The returned functor references `tunnels`, which must outlive it.
    static TunnelLookup makeTunnelLookup(const TunnelTileMap& tunnels);
    // Draws the green connection highlight for every selected tunnel end that has a
    // matching end (REQ-BLD-TUNNEL-SELECT-HIGHLIGHT).
    void         drawSelectedTunnelConnections(QPainter& painter);

    // Belt drag placement (REQ-BLD-BELT-DRAG).
    // Per-path-tile decision, shared by ghost drawing and release-time placement.
    enum class BeltTileAction { PlaceNew, RotateInPlace, Invalid };
    struct BeltDragResolved
    {
        BeltTileAction            action;
        bool                      affordable;   // meaningful only for PlaceNew
        std::optional<BuildingId> rotateId;     // set only for RotateInPlace
    };
    // Recomputes m_beltDragPath from m_beltDragAnchor to cursorTile using the
    // current ghost orientation.
    void                          recomputeBeltDragPath(QPoint cursorTile);
    // Classifies each path tile against the current sim state, applying cumulative
    // affordability to the PlaceNew tiles.
    std::vector<BeltDragResolved> resolveBeltDragPath() const;
    // Enqueues placements and rotate-in-place commands for the resolved path.
    void                          applyBeltDragPath();

    // Copy-settings gesture (REQ-BLD-COPY-CONFIG): Shift+right-click copies a
    // building's configuration into m_copiedConfig; Shift+left-click applies it to
    // another building of the same type via the existing configuration commands.
    void     copyConfigFrom(BuildingId id);
    void     pasteConfigTo(BuildingId id);

    void     enterBuilderMode(BuildingType type);
    void     exitBuilderMode();
    void     enterBlueprintMode(Blueprint blueprint);
    void     exitBlueprintMode();
    void     toggleDeconstructMode();
    void     rotateGhost(bool clockwise);

    struct ActiveBeam
    {
        BeamFiredEvent event;
        QVector2D targetOffset;
    };

    // Beam lifetime in game ticks so beams freeze with the simulation when
    // paused or slowed, instead of fading on wall-clock time (REQ-SHP-FIRING-BEAM).
    static constexpr Tick    kBeamLifetimeTicks      = secondsToTicks(0.3);

    Simulation*          m_sim;
    const GameConfig*    m_config;
    const VisualsConfig* m_visuals;

    // World icon glyph renderers per building type (REQ-UI-WORLD-ICON), in a
    // white and a dark variant so drawBuildingIcon can auto-contrast against the
    // building's fill. Rendered as vector at the view scale each draw so they
    // stay crisp. Populated once by loadBuildingIcons().
    struct BuildingIconRenderers
    {
        std::unique_ptr<QSvgRenderer> white;
        std::unique_ptr<QSvgRenderer> dark;
    };
    std::map<BuildingType, BuildingIconRenderers> m_buildingIcons;

    // Per-item icon cache (REQ-UI-ITEM-ICON), shared window-wide and owned by
    // MainWindow. Shared draw path for belt and port items; pixmaps are cached
    // per target size.
    ItemIconCache* m_itemIcons;

    // Funnels all player input into the single Simulation::apply chokepoint.
    CommandManager m_commandManager;
    // A Reset command was enqueued; reset the view after the next drain applies it.
    bool           m_viewResetPending = false;
    // Non-null => view-only playback: ticks are driven by the recorded stream and
    // live input is ignored (the CommandManager is in replay mode).
    std::unique_ptr<ReplayPlayer> m_replayPlayer;

    TickDriver    m_tickDriver;
    QElapsedTimer m_frameTimer;
    std::mt19937  m_rng;
    double        m_gameSpeedMultiplier;
    double        m_prevNonZeroSpeed;
    // World-X (tiles) at the center of the viewport (see getViewLeftTiles()).
    float         m_scrollXTiles;

    QTimer* m_renderTimer;

    std::vector<ActiveBeam> m_activeBeams;

    std::optional<BuildingType>          m_builderType;
    Rotation                             m_ghostRotation;
    QPoint                               m_ghostTile;
    bool                                 m_ghostValid;
    // Unified tunnel build mode (REQ-BLD-TUNNEL-MODE): while m_builderType is
    // TunnelEntry the ghost resolves to an entry or an exit based on the hovered
    // position; m_tunnelPartnerTile is the existing tunnel it would complete (drawn
    // as the green connection preview), or unset when there is no completion match.
    BuildingType                         m_tunnelGhostType = BuildingType::TunnelEntry;
    std::optional<QPoint>                m_tunnelPartnerTile;
    // Deferred belt drag placement (REQ-BLD-BELT-DRAG): while dragging, the
    // rectilinear anchor->cursor path is recomputed on each move and only applied
    // on release. Empty unless a belt drag is in progress.
    std::vector<BeltPathTile>            m_beltDragPath;
    QPoint                               m_beltDragAnchor;
    // Last known cursor position in world (tile) units; used to pick the belt-drag
    // end tile closest to the cursor when snapping to a building (REQ-BLD-BELT-DRAG).
    QVector2D                            m_cursorWorldPos;
    bool                                 m_dragging;

    std::optional<Blueprint> m_blueprintMode;
    QPoint                   m_blueprintGhostTile;

    // Temporary cache for the copy-settings gesture (REQ-BLD-COPY-CONFIG); held
    // only while Shift is down and cleared on Shift release.
    std::optional<BuildingConfig> m_copiedConfig;

    // Brief outline flash shown on a building when settings are copied from it or
    // pasted onto it (REQ-BLD-COPY-CONFIG-FEEDBACK). remainingMs counts down in
    // wall-clock time so the flash plays at a fixed length regardless of game speed
    // (and while paused).
    struct CopyConfigFlash
    {
        BuildingId id;
        qint64     remainingMs;
    };
    std::vector<CopyConfigFlash> m_copyConfigFlashes;
    static constexpr qint64      kCopyFlashDurationMs = 300;

    bool     m_deconstructMode;
    std::optional<BuildingId> m_deconstructHoverBuildingId;
    bool     m_debugDraw;

    std::vector<BuildingId> m_selectedBuildingIds;
    std::vector<entt::entity> m_selectedEntities;
    std::vector<entt::entity> m_selectedDebris;
    bool                  m_boxSelecting;
    QPoint                m_boxStartTile;
    QPoint                m_boxCurrentTile;

    bool m_scrollLeft;
    bool m_scrollRight;
    bool m_gameOverShown;
    bool m_winShown;
    bool m_schematicChoiceShown;

    Tick m_lastTick          = Tick(-1);
    int  m_lastBlocks        = -1;
    int  m_lastExpansionCost = -1;
    int  m_lastBossCounter   = -1;
    Tick m_lastBossCountdown = Tick(-1);
    int  m_lastArtifactCount = -1;
    int  m_lastUnlockedBuildingCount = -1;
};
