#pragma once

#include <QPoint>
#include <QRect>
#include <QWidget>

#include <optional>
#include <vector>

#include "DebrisSelectionChangedEvent.h"
#include "DebugDrawToggledEvent.h"
#include "EntitySelectionChangedEvent.h"
#include "EventHandler.h"
#include "FloatingPanel.h"
#include "FloatingPanelPlacement.h"
#include "PlayerCommandsAppliedEvent.h"
#include "SelectionAnchorChangedEvent.h"
#include "SelectionChangedEvent.h"
#include "TickAdvancedEvent.h"
#include "selection/SelectionContentFactory.h"
#include "selection/SelectionContext.h"

struct GameConfig;
struct VisualsConfig;
class BuildingIconCache;
class ItemIconCache;
class SelectionContent;
class Simulation;
class QMouseEvent;
class QScrollArea;
class QVBoxLayout;

// Shows the current selection. The panel itself renders nothing: it arbitrates between
// the two selection categories (REQ-UI-SELECTION-CATEGORIES), picks the card that fits
// what is selected (REQ-UI-SELECTION-CONTENT), and hosts exactly one of them at a time.
// What each card looks like lives in src/ui/selection/.
//
// The panel floats over the game world view rather than occupying a column of its own
// (REQ-UI-SELECTION-PANEL): it sizes itself to its card, places itself in what the build
// button bar and the controls panel have left free, and hides itself entirely while
// nothing is selected (REQ-UI-EMPTY-SELECTION).
class SelectionPanel : public QWidget,
                       public FloatingPanel,
                       public CombinedEventHandler<TickAdvancedEvent,
                                                   PlayerCommandsAppliedEvent,
                                                   EntitySelectionChangedEvent,
                                                   SelectionChangedEvent,
                                                   SelectionAnchorChangedEvent,
                                                   DebrisSelectionChangedEvent,
                                                   DebugDrawToggledEvent>
{
    Q_OBJECT

public:
    // visuals, itemIcons and buildingIcons are window-wide rendering resources the cards
    // draw from; none is owned and all must outlive this widget.
    SelectionPanel(Simulation* sim, const GameConfig* config,
                   const VisualsConfig* visuals, ItemIconCache* itemIcons,
                   BuildingIconCache* buildingIcons, QWidget* parent = nullptr);
    ~SelectionPanel() override;

    // Sizes the panel to its card and places it beside the selection it describes, in
    // what the widgets placed before it have left free. Keeping clear of them is entirely
    // this panel's job; neither of them ever moves for it (REQ-UI-SELECTION-PANEL,
    // REQ-UI-BUILD-BAR, REQ-UI-CONTROLS-PANEL).
    void placeIn(const QRect& viewRect,
                 const std::vector<QRect>& occupiedRects) override;

protected:
    // The panel is moved by dragging its card header (REQ-UI-SELECTION-PANEL-DRAG). Every
    // other mouse event over the panel is swallowed here so that none of them reaches the
    // game world beneath (REQ-UI-SELECTION-PANEL).
    void mousePressEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* event) override;

private:
    void handleEvent(std::shared_ptr<const SelectionChangedEvent> event) override;
    void handleEvent(std::shared_ptr<const SelectionAnchorChangedEvent> event) override;
    void handleEvent(std::shared_ptr<const EntitySelectionChangedEvent> event) override;
    void handleEvent(std::shared_ptr<const DebrisSelectionChangedEvent> event) override;
    void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
    void handleEvent(std::shared_ptr<const PlayerCommandsAppliedEvent> event) override;
    void handleEvent(std::shared_ptr<const DebugDrawToggledEvent> event) override;

    // Re-reads the live values of the card on screen. When the selection now calls for a
    // different card -- a construction site finishing is the case that matters -- it
    // rebuilds instead, because a card never changes its own shape.
    void refreshContent();
    // Replaces the card with the one the current selection calls for.
    void rebuildContent();
    // Asks for the placement pass to be re-run, the card having changed size or the
    // panel having gained or lost its reason to be shown at all.
    void invalidateLayout();

    SelectionContext m_context;
    // Read through m_context by the cards that need it, so a toggle reaches the card on
    // screen without it having to subscribe to the event itself.
    bool m_debugDrawEnabled = false;

    SelectionRequest  m_request;
    ContentKey        m_contentKey;
    SelectionContent* m_content = nullptr;

    // Where the current selection was on the screen when it started, in the game world
    // view's coordinates, the gap the panel keeps from it, and which side of it the panel
    // took. All three are frozen for as long as the selection lasts: the anchor because
    // the panel does not chase a scrolling view or a moving ship, the gap because it is
    // measured against that frozen rectangle, the side because a card that grows must not
    // flip the panel across the object (REQ-UI-SELECTION-PANEL). The side is resolved on
    // the first placement after a new anchor, being the first point at which the panel's
    // width is known. Dragging the panel supersedes all three for the rest of the
    // selection (REQ-UI-SELECTION-PANEL-DRAG).
    QRect                    m_anchorRect;
    int                      m_selectionGapPx = 0;
    std::optional<PanelSide> m_side;

    // Where the player dragged the panel, in the game world view's coordinates, and the
    // cursor's offset within the panel while a drag is running
    // (REQ-UI-SELECTION-PANEL-DRAG). The desired position is kept exactly as dropped:
    // resolving it against the view's edges and the widgets the panel steps around never
    // writes back to it, so the panel returns to it once the room is there again. It
    // lasts as long as the selection it was set in.
    std::optional<QPoint> m_desiredTopLeftPx;
    std::optional<QPoint> m_dragGrabOffsetPx;
    // Origin of the view the panel was last placed in, which is what the two coordinate
    // systems differ by -- the panel is a sibling of the view, not a child of it.
    QPoint m_viewOriginPx;

    // Scrolls the card once it outgrows the space the panel has (REQ-UI-SELECTION-PANEL).
    // The card is a child of m_body, not of the panel itself.
    QScrollArea* m_scrollArea;
    QWidget*     m_body;
    QVBoxLayout* m_bodyLayout;
};
