#pragma once

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include <QWidget>

#include "BlueprintSaveRequestedEvent.h"
#include "BlueprintSelectionRequestedEvent.h"
#include "BuildingId.h"
#include "EscapeMenuRequestedEvent.h"
#include "EventHandler.h"
#include "FloatingLayoutInvalidatedEvent.h"
#include "GameConfig.h"
#include "GameOverEvent.h"
#include "ItemTooltipContext.h"
#include "LayoutDialogRequestedEvent.h"
#include "ModalLayer.h"
#include "WinEvent.h"
#include "RecipeSelectionRequestedEvent.h"
#include "SchematicChoicesAvailableEvent.h"
#include "ShipLayout.h"
#include "ViewScrolledEvent.h"
#include "ShipLayoutBlueprint.h"
#include "Tick.h"
#include "VisualsConfig.h"

struct ParsedReplay;
class Simulation;
class GameWorldView;
class HeaderBar;
class SelectionPanel;
class ControlsPanel;
class BuildButtonBar;
class BlueprintLibrary;
class BuildingIconCache;
class ExpandButton;
class ItemIconCache;
class QCloseEvent;
class QResizeEvent;

class MainWindow : public QWidget,
                   public CombinedEventHandler<SchematicChoicesAvailableEvent,
                                               GameOverEvent,
                                               WinEvent,
                                               EscapeMenuRequestedEvent,
                                               LayoutDialogRequestedEvent,
                                               RecipeSelectionRequestedEvent,
                                               BlueprintSaveRequestedEvent,
                                               BlueprintSelectionRequestedEvent,
                                               FloatingLayoutInvalidatedEvent,
                                               ViewScrolledEvent>
{
    Q_OBJECT

public:
    MainWindow(Simulation* sim, const std::string& configDir,
               std::shared_ptr<ParsedReplay> replay = nullptr, QWidget* parent = nullptr);
    ~MainWindow() override;

protected:
    void resizeEvent(QResizeEvent* event) override;
    void closeEvent(QCloseEvent* event) override;

private:
    void handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event) override;
    void handleEvent(std::shared_ptr<const GameOverEvent> event) override;
    void handleEvent(std::shared_ptr<const WinEvent> event) override;
    void handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const BlueprintSaveRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const BlueprintSelectionRequestedEvent> event) override;
    void handleEvent(std::shared_ptr<const FloatingLayoutInvalidatedEvent> event) override;
    void handleEvent(std::shared_ptr<const ViewScrolledEvent> event) override;

    // Reloads the game config and visuals.toml from disk (REQ-CFG-RELOAD), shared
    // by every restart path. On success the reloaded visuals are applied to this
    // window and the fresh GameConfig is returned; on failure a modal error dialog
    // is shown and std::nullopt is returned, leaving the window state untouched.
    std::optional<GameConfig> reloadConfig();

    // Opens the shipyard layout configuration dialog for the given schematic and
    // current layout, applying the result via SetShipLayoutCommand (REQ-MOD-UI-DIALOG).
    void openShipLayoutDialog(BuildingId shipyardId,
                              const std::string& schematicId,
                              const ShipLayoutConfig& currentLayout);

    // The rectangle a modal opened from the selection panel is centered on, in this
    // window's coordinates, or a null rect when the panel is not up (REQ-UI-PANEL-MODAL).
    QRect getSelectionPanelAnchor() const;

    // Runs the blueprint selection dialog and enters placement mode for whatever the
    // player picked (REQ-UI-BLUEPRINT-DIALOG). Holds no pause scope of its own: both
    // callers already hold theirs, and the save path also holds the layer, which is what
    // keeps the dim continuous when a confirmed save hands straight over to this dialog
    // (REQ-UI-MODAL-DIM).
    void showBlueprintSelectionDialog();
    // What every display naming an item needs to explain it (REQ-UI-ITEM-VALUE-TOOLTIP).
    // Assembled here because this is where the simulation and the icon caches live.
    ItemTooltipContext getItemTooltipContext() const;
    // Centers the expansion button on the columns the next purchase unlocks
    // (REQ-UI-EXPAND-BUTTON). Its place is in the world, so it is re-derived whenever the
    // view scrolls or resizes rather than once per selection like the panels.
    void placeExpandButton();
    // Places the widgets floating over the game world view, in one ordered pass
    // (FloatingPanel.h). Runs on a resize and on every FloatingLayoutInvalidatedEvent.
    void layoutPanels();

private:
    std::string            m_configDir;
    VisualsConfig          m_visuals;
    Simulation*            m_sim;
    // One per-item icon cache for the whole window (REQ-UI-ITEM-ICON): the header,
    // build bar, world view, and recipe dialog all rasterize the same SVGs.
    std::unique_ptr<ItemIconCache> m_itemIcons;
    // Likewise one per-building chip cache (REQ-UI-BUILD-ICON), shared by the build bar
    // and the selection panel's card headers (REQ-UI-SELECTION-CARD).
    std::unique_ptr<BuildingIconCache> m_buildingIcons;
    GameWorldView*         m_gameWorldView;
    HeaderBar*             m_headerBar;
    SelectionPanel*        m_selectionPanel;
    ControlsPanel*         m_controlsPanel;
    BuildButtonBar*        m_buildButtonBar;
    // Stands in the world on the ground it would buy, so it takes no part in the panels'
    // placement pass (REQ-UI-EXPAND-BUTTON).
    ExpandButton*          m_expandButton = nullptr;
    // The saved blueprints themselves; they have no widget of their own any more and
    // are reached through the two modal dialogs (REQ-UI-BLUEPRINT-DIALOG).
    std::unique_ptr<BlueprintLibrary> m_blueprintLibrary;
    ModalLayer*            m_modalLayer = nullptr;

    std::vector<ShipLayoutBlueprint> m_layoutBlueprints;
    std::shared_ptr<ParsedReplay>    m_replay;  // non-null => view-only playback

    // Set while the placement pass runs, so a widget placed in it cannot start a second
    // pass from inside the first.
    bool m_layingOut = false;
};
