#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 "BuildModeController.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 "DebugDrawToggleRequestedEvent.h"
#include "GhostRotationRequestedEvent.h"
#include "InputMapper.h"
#include "ModeCancelRequestedEvent.h"
#include "PanDirectionChangedEvent.h"
#include "PauseToggleRequestedEvent.h"
#include "SelectionClearRequestedEvent.h"
#include "SpeedStepRequestedEvent.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 "PlacementRules.h"
#include "Rotation.h"
#include "SelectionController.h"
#include "Tick.h"
#include "TickDriver.h"
#include "TunnelCompletion.h"
#include "VisualsConfig.h"
#include "WorldCamera.h"
#include "WorldCoordinates.h"
#include "WorldRenderer.h"

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

class GameWorldView : public QOpenGLWidget,
                      public CombinedEventHandler<BeamFiredEvent,
                                                  BuildingTypeSelectedEvent,
                                                  ExitBuilderModeRequestedEvent,
                                                  DeconstructModeToggleRequestedEvent,
                                                  BlueprintPlacementRequestedEvent,
                                                  ExitBlueprintModeRequestedEvent,
                                                  SpeedChangeRequestedEvent,
                                                  PanDirectionChangedEvent,
                                                  PauseToggleRequestedEvent,
                                                  SpeedStepRequestedEvent,
                                                  GhostRotationRequestedEvent,
                                                  ModeCancelRequestedEvent,
                                                  SelectionClearRequestedEvent,
                                                  DebugDrawToggleRequestedEvent,
                                                  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();

    // The blueprint library is constructed after this widget, so it arrives by setter.
    // Not owned; supplies the two facts about blueprints that the control context needs
    // (REQ-UI-CONTROLS-CONTENT).
    void setBlueprintLibrary(const BlueprintLibrary* library);

    // The player's current situation, as the one snapshot every reader of the control
    // table works from: this widget's own mouse dispatch, the input mapper, and the
    // controls panel (REQ-UI-CONTROLS-CONTENT). Built here because this widget owns the
    // build mode and the selection.
    ControlContext getControlContext() const;

    // Name of the blueprint being placed, for the controls panel's heading. Empty while
    // no blueprint is active and for the unnamed temporary one (REQ-UI-BLUEPRINT-TEMP).
    QString getActiveBlueprintName() const;

    // The world <-> widget transform for the current viewport size and scroll
    // position. Cheap to build and deliberately not cached: it is a snapshot that
    // a resize or a scroll invalidates, so every user takes a fresh one. Public because
    // a widget standing in the world needs it to find its place (REQ-UI-EXPAND-BUTTON);
    // a ViewScrolledEvent says when it has gone stale.
    WorldCoordinates getCoordinates() const;

protected:
    void initializeGL() override;
    void paintGL() override;
    // Only forwards to the input mapper; every key this widget acts on reaches it
    // as a published action instead (REQ-UI-HOTKEYS).
    void keyPressEvent(QKeyEvent* event) override;
    void keyReleaseEvent(QKeyEvent* event) override;
    // Key-up never arrives for a key that was still held when focus moved away —
    // to a modal dialog, another widget, or another window. Any held action would
    // otherwise stay held forever, so focus loss drops all of them.
    void focusOutEvent(QFocusEvent* 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 PanDirectionChangedEvent> event) override;
    void handleEvent(std::shared_ptr<const PauseToggleRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const SpeedStepRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const GhostRotationRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const ModeCancelRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const SelectionClearRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const DebugDrawToggleRequestedEvent> 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;

    // Screen-anchored chrome, drawn after the world (see WorldRenderer): these
    // need no world transform, which is exactly why they stayed here.
    void drawScreenSpace(QPainter& painter);
    // Threat and production readout shown while debug draw is on (F3). Positioned
    // in pixels at the top-left corner, so it belongs with the chrome rather than
    // with the world.
    void drawDebugOverlay(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);

    // Gathers the interaction state the renderer needs for this frame.
    WorldRenderFrame makeRenderFrame() const;

    float    getAsteroidLeftEdge() const;
    float    getEnemyStationRightEdge() const;
    // The camera's current pan limits, read fresh from the simulation each frame:
    // both edges move with asteroid expansion and with pushes (REQ-GW-SCROLL-LIMIT).
    ScrollBounds getScrollBounds() const;

    // canPlaceBuilding (PlacementRules) against this view's simulation.
    bool     canPlaceBuildingHere(BuildingType type, QPoint anchor, Rotation rot) const;
    std::optional<BuildingId> buildingAtTile(QPoint tile) const;
    std::optional<BuildingId> siteAtTile(QPoint tile) const;

    void     placeBlueprintAtTile(QPoint center);
    // resolveBlueprintGhost (PlacementRules) for one building of the blueprint currently
    // in placement mode, anchored at the cursor tile `center`.
    BlueprintGhostResolved resolveBlueprintGhostHere(const BlueprintBuilding& building,
                                                     QPoint center) const;
    // The blueprint's stored recipe or schematic id, or empty when it stores none or the
    // stored one is currently locked (REQ-LOCK-UI-BLUEPRINT).
    std::string unlockedRecipeId(const BlueprintBuilding& building) const;
    // Applies a single-building blueprint's settings to the building it is hovering,
    // instead of placing anything (REQ-UI-BLUEPRINT-TRANSFER).
    void     transferConfigTo(BuildingId id, const BlueprintBuilding& source);

    // Drops despawned or fully-collected debris from the selection
    // (REQ-UI-DEBRIS-CLICK-SELECT). Called each frame from onFrame().
    void                     pruneDespawnedDebris();
    // Drops despawned or dead actors from the selection (REQ-UI-ENTITY-CLICK-SELECT).
    // Called each frame from onFrame().
    void                     pruneDespawnedActors();
    // Resolves a click into the selection it should produce, applying the category
    // precedence of REQ-UI-SELECTION-CATEGORIES; the controller owns what that then
    // does to the existing selection. Returns false when the click hit nothing,
    // which is the only case that goes on to start a box drag.
    bool                     selectAtPoint(QPoint tile, QVector2D worldPos, bool additive);
    void                     selectInBox(bool additive);
    // A build mode and a selection are mutually exclusive, so entering any mode drops
    // the selection (REQ-UI-SELECTION-EXCLUSIVE). Called from each of the three events
    // that enter a mode -- the only ways in, whichever button or key the player used.
    // Clearing an empty selection publishes nothing, so the exiting half of the
    // deconstruct toggle costs nothing.
    void                     clearSelectionForBuildMode();
    // Whether the cursor points at the game world at all: it does while it is over
    // this widget, and while a belt or box drag holds the button, which goes on
    // following the cursor onto the floating panels and past the window edge.
    bool                     isHoverLive() const;
    // Re-derives the hover from wherever the cursor is now, or drops it when the
    // cursor points at nothing (REQ-BLD-GHOST). The entry point for everything a
    // mouse move does not cover: a scrolling view, a cursor crossing onto a panel or
    // out of the window, and a mode just entered under a cursor that has not moved.
    void                     refreshHover();
    // Re-resolves everything that follows from where the cursor points into the world:
    // the ghost tile and its validity, the tunnel ends, a running belt or box drag, the
    // deconstruct hover. Called for every mouse move, and once per frame while the view
    // scrolls under a cursor that has not moved, since that changes the world position
    // the cursor points at just as moving the mouse does (REQ-BLD-GHOST).
    void                     updateHoverAt(QPoint cursorWidgetPos);
    // Moves the running box drag's far corner to the world position under
    // `cursorWidgetPos` and, once the cursor sits far enough from where the anchor is
    // drawn, promotes the gesture from a click to a drag. Every corner update goes
    // through here, including the ones a scrolling view causes under a cursor that
    // has not moved (REQ-UI-MULTI-SELECT).
    void                     updateBoxDrag(QPoint cursorWidgetPos);
    // The box the drag currently spans, in world coordinates and normalized: the
    // rectangle between its two corners once it reads as a drag, and the whole tile
    // the button went down on before that (REQ-UI-MULTI-SELECT). Both what is drawn
    // and what is selected come from here, so they can never disagree.
    QRectF                   getBoxWorldRect() const;
    // Publishes where on the screen the selection about to be made sits, so the
    // selection panel can be placed beside it (REQ-UI-SELECTION-PANEL). Called with
    // what is about to be selected, immediately before selecting it, and publishes
    // nothing unless that selection is starting rather than growing.
    void                     publishSelectionAnchor(
                                 SelectionMode mode,
                                 const std::vector<BuildingId>& buildings,
                                 const std::vector<entt::entity>& actors,
                                 const std::vector<entt::entity>& debris);
    void                     stepSpeed(int delta);
    void                     placeAtTile(QPoint tile);

    // Re-resolves the tunnel ghost type and completion partner from the current
    // ghost tile, rotation, and sub-tile cursor position, storing both on the build
    // mode controller. Only meaningful in tunnel mode (REQ-BLD-TUNNEL-MODE).
    void         updateTunnelGhost();
    // Belt drag placement (REQ-BLD-BELT-DRAG).
    // Recomputes the drag path from its anchor to cursorTile using the current ghost
    // orientation, and stores it on the build mode controller.
    void                          recomputeBeltDragPath(QPoint cursorTile);
    // resolveBeltDragPath (PlacementRules) against this view's simulation.
    std::vector<BeltDragResolved> resolveBeltDragPath() const;
    // Enqueues placements and rotate-in-place commands for the resolved path.
    void                          applyBeltDragPath();

    // Turns the ghost and refreshes everything that depends on its facing: placement
    // validity, the tunnel completion match, and an in-progress belt drag's path.
    // The mode transitions themselves live on m_buildMode.
    void     rotateGhost(bool clockwise);

    // 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);

    // How far the cursor must travel from the press position, in widget pixels
    // (Manhattan distance), before a box drag shows its rectangle
    // (REQ-UI-MULTI-SELECT).
    static constexpr int     kBoxDragThresholdPixels = 2;

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

    // 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;

    // Draws the world; this widget supplies the interaction state each frame and
    // keeps only the screen-anchored chrome for itself.
    std::unique_ptr<WorldRenderer> m_renderer;

    TickDriver    m_tickDriver;
    QElapsedTimer m_frameTimer;
    std::mt19937  m_rng;
    double        m_gameSpeedMultiplier;
    double        m_prevNonZeroSpeed;
    // Horizontal view position (REQ-UI-SCROLL). Owns the scroll position itself;
    // this widget only supplies the pan intent and the simulation-derived bounds.
    WorldCamera   m_camera;

    QTimer* m_renderTimer;

    std::vector<ActiveBeam> m_activeBeams;

    // The active build mode (builder / blueprint / deconstruct, or none) and the
    // ghost, tunnel and belt-drag state belonging to it. Owns the transitions
    // between them; this widget supplies the parts that need the simulation.
    BuildModeController m_buildMode;

    // 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)
    // and to resolve the tunnel ghost sub-tile (REQ-BLD-TUNNEL-MODE).
    QVector2D                            m_cursorWorldPos;
    // Whether the hover state currently stands for a cursor pointing at the world,
    // so that losing it is noticed once rather than every frame. Kept here rather
    // than asked of Qt per reader: it has to agree with what was last written to the
    // build mode controller, not with where the cursor happens to be mid-frame.
    bool                                 m_hoverLive = false;

    bool     m_debugDraw;

    // Owns the selection across all three categories and the rules for moving
    // between them (REQ-UI-SELECTION-CATEGORIES), including publishing the change
    // events. This widget only resolves what was hit.
    SelectionController   m_selection;
    // Not owned; set after construction, so null until MainWindow has built it.
    const BlueprintLibrary* m_blueprintLibrary = nullptr;
    bool                  m_boxSelecting;
    // The drag's two corners in world coordinates, unsnapped: where the button went
    // down and where the cursor is now (REQ-UI-MULTI-SELECT). World rather than
    // widget coordinates so the anchor keeps the spot in the world it was placed on
    // when the view scrolls under a held button.
    QVector2D             m_boxStartWorld;
    QVector2D             m_boxCurrentWorld;
    // Whether the cursor has moved far enough from the anchor for this to read as a
    // drag. Until it has, the rectangle is not drawn and the box resolves as the
    // whole anchor tile (REQ-UI-MULTI-SELECT). Sticky for the rest of the drag, so
    // coming back to the press position does not hide the rectangle again.
    bool                  m_boxDragMoved;

    // Interprets this widget's key events into semantic actions and publishes them
    // (REQ-UI-HOTKEYS). Owned here for now because this is the widget that holds
    // focus; everything it produces travels by event, so it can move to a
    // window-wide owner later without touching its consumers.
    InputMapper m_inputMapper;

    // Latest pan direction published by the input mapper (REQ-UI-SCROLL), fed to
    // the camera each frame. Cached from the event payload rather than re-read:
    // input has no other source of truth (see PanDirectionChangedEvent).
    PanDirection m_panDirection = PanDirection::None;
    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;
};
