#include "BlueprintLibrary.h"

#include <cstddef>
#include <utility>

#include <QCoreApplication>
#include <QFile>
#include <QMessageBox>
#include <QObject>
#include <QStringList>

#include "BlueprintPlacementRequestedEvent.h"
#include "BlueprintSerializer.h"
#include "EventManager.h"
#include "ExitBlueprintModeRequestedEvent.h"

#include "BuildingConfig.h"
#include "Simulation.h"

BlueprintLibrary::BlueprintLibrary(Simulation* sim, const GameConfig* config,
                                   QWidget* dialogParent)
    : m_sim(sim)
    , m_config(config)
    , m_dialogParent(dialogParent)
{
    loadFromDisk();
    registerForEvents();
}

BlueprintLibrary::~BlueprintLibrary()
{
    saveToDisk();
    unregisterForEvents();
}

bool BlueprintLibrary::getCanCaptureSelection() const
{
    // A construction site counts the same as an operational building (REQ-UI-BLUEPRINT-CREATE).
    return selectionHasPlaceableBuilding(*m_sim, m_selectedBuildingIds);
}

bool BlueprintLibrary::getHasTemporaryBlueprint() const
{
    return m_temporaryBlueprint.has_value();
}

void BlueprintLibrary::saveSelectionAs(const QString& name)
{
    Blueprint blueprint = createBlueprintFromSelection();
    if (blueprint.buildings.empty()) { return; }

    blueprint.name = name;
    m_blueprints.push_back(std::move(blueprint));
}

const std::vector<Blueprint>& BlueprintLibrary::getBlueprints() const
{
    return m_blueprints;
}

QString BlueprintLibrary::getContentsSummary(int index) const
{
    if (index < 0 || index >= static_cast<int>(m_blueprints.size())) { return QString(); }

    QStringList entries;
    for (const BlueprintContentEntry& entry : summarizeBlueprintContents(
             m_blueprints[static_cast<std::size_t>(index)], m_config->buildings))
    {
        // The same "<type> x <count>" notation the building multi-selection summary
        // uses (REQ-UI-MULTI-SELECTION), which also sidesteps plural forms.
        entries << QObject::tr("%1 x %2")
            .arg(QString::fromStdString(entry.buildingName))
            .arg(entry.count);
    }
    return entries.join(QStringLiteral(", "));
}

int BlueprintLibrary::getCost(int index) const
{
    if (index < 0 || index >= static_cast<int>(m_blueprints.size())) { return 0; }
    return computeBlueprintCost(m_blueprints[static_cast<std::size_t>(index)],
                                m_config->buildings);
}

bool BlueprintLibrary::getCanAfford(int index) const
{
    return m_sim->getBuildingBlocksStock() >= getCost(index);
}

void BlueprintLibrary::remove(int index)
{
    if (index < 0 || index >= static_cast<int>(m_blueprints.size())) { return; }

    if (m_activeIndex == index)
    {
        m_activeIndex = std::nullopt;
        EventManager::getInstance()->sendEventImmediately(
            std::make_shared<ExitBlueprintModeRequestedEvent>());
    }
    else if (m_activeIndex.has_value() && *m_activeIndex > index)
    {
        --*m_activeIndex;
    }
    m_blueprints.erase(m_blueprints.begin() + index);
}

void BlueprintLibrary::beginPlacement(int index)
{
    if (index < 0 || index >= static_cast<int>(m_blueprints.size())) { return; }

    m_activeIndex = index;
    EventManager::getInstance()->sendEventImmediately(
        std::make_shared<BlueprintPlacementRequestedEvent>(
            m_blueprints[static_cast<std::size_t>(index)]));
}

void BlueprintLibrary::handleEvent(std::shared_ptr<const SelectionChangedEvent> event)
{
    m_selectedBuildingIds = event->ids;
}

void BlueprintLibrary::handleEvent(std::shared_ptr<const BlueprintModeExitedEvent> /*event*/)
{
    // Only the saved-blueprint index is cleared. A temporary blueprint deliberately
    // survives its placement mode so V can re-enter it (REQ-UI-BLUEPRINT-TEMP).
    m_activeIndex = std::nullopt;
}

void BlueprintLibrary::handleEvent(
    std::shared_ptr<const TemporaryBlueprintCaptureRequestedEvent> /*event*/)
{
    // C (REQ-UI-BLUEPRINT-TEMP): capture the current selection and enter placement mode
    // for it, without naming it, listing it, or persisting it. If nothing player-placeable
    // is selected, do nothing at all -- in particular, keep the previous temporary
    // blueprint, which V can still place.
    Blueprint blueprint = createBlueprintFromSelection();
    if (blueprint.buildings.empty()) { return; }

    m_temporaryBlueprint = std::move(blueprint);
    // No saved blueprint is active while a temporary one is being placed.
    m_activeIndex = std::nullopt;
    EventManager::getInstance()->sendEventImmediately(
        std::make_shared<BlueprintPlacementRequestedEvent>(*m_temporaryBlueprint));
}

void BlueprintLibrary::handleEvent(
    std::shared_ptr<const TemporaryBlueprintPlaceRequestedEvent> /*event*/)
{
    // V (REQ-UI-BLUEPRINT-TEMP): re-enter placement mode for the temporary blueprint,
    // capturing nothing. With none captured, nothing happens -- no mode is entered and
    // whichever mode is active is left alone. Affordability is not checked here, matching
    // C; cost is enforced at placement (REQ-UI-BLUEPRINT-PLACE). The blueprint is copied,
    // not moved: it stays available for the next V.
    if (!m_temporaryBlueprint.has_value()) { return; }

    m_activeIndex = std::nullopt;
    EventManager::getInstance()->sendEventImmediately(
        std::make_shared<BlueprintPlacementRequestedEvent>(*m_temporaryBlueprint));
}

void BlueprintLibrary::handleEvent(std::shared_ptr<const GameResetEvent> /*event*/)
{
    // A restart begins a new run, and the temporary blueprint belongs to the old one
    // (REQ-UI-BLUEPRINT-TEMP). The saved blueprints are not run state and stay.
    m_temporaryBlueprint = std::nullopt;
}

Blueprint BlueprintLibrary::createBlueprintFromSelection() const
{
    // Capture is shared, testable logic in lib/sim: it resolves each selected id as an
    // operational building or a construction site alike (REQ-UI-BLUEPRINT-CREATE,
    // REQ-UI-BLUEPRINT-STORAGE).
    return captureBlueprintFromSelection(*m_sim, m_selectedBuildingIds);
}

void BlueprintLibrary::loadFromDisk()
{
    // Load at startup (REQ-UI-BLUEPRINT-LOAD). Missing file: start empty, no error.
    const QString path = QCoreApplication::applicationDirPath() + "/blueprints.toml";
    QFile file(path);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return; }
    try
    {
        m_blueprints = BlueprintSerializer::deserialize(file.readAll().toStdString());
    }
    catch (const std::exception& e)
    {
        QMessageBox::critical(m_dialogParent, QObject::tr("Load Failed"),
            QObject::tr("Failed to load blueprints:\n%1").arg(e.what()));
        m_blueprints.clear();
    }
}

void BlueprintLibrary::saveToDisk() const
{
    // Persist on shutdown; write errors are silently ignored (REQ-UI-BLUEPRINT-SAVE).
    const QString path = QCoreApplication::applicationDirPath() + "/blueprints.toml";
    QFile file(path);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { return; }
    try
    {
        const std::string content = BlueprintSerializer::serialize(m_blueprints);
        file.write(QByteArray::fromStdString(content));
    }
    catch (...) {}
}
