#include "SelectionContent.h"

#include <algorithm>
#include <string>

#include <QFont>
#include <QHBoxLayout>
#include <QLabel>
#include <QVBoxLayout>

#include "BarRow.h"
#include "BuildingIconCache.h"
#include "EmptyNote.h"
#include "FactoryQueries.h"
#include "GameConfig.h"
#include "ProductionRules.h"
#include "SectionBox.h"
#include "Simulation.h"
#include "StatusPill.h"
#include "Tick.h"
#include "VisualsConfig.h"

namespace
{

// Size of the identity chip in the card header, in device-independent pixels. Smaller
// than the build button's 32 px chip: here it labels a line of text rather than being
// the whole button face.
const int kSymbolSizePx = 20;

// Spacing inside the card and between the header's elements.
const int kCardSpacingPx   = 6;
const int kHeaderSpacingPx = 6;

}  // namespace


SelectionContent::SelectionContent(const SelectionContext& context,
                                   std::optional<BuildingId> constructionSiteId,
                                   QWidget* parent)
    : QWidget(parent)
    , m_context(context)
    , m_siteId(constructionSiteId)
    , m_constructionSection(nullptr)
    , m_constructionBar(nullptr)
{
    QVBoxLayout* cardLayout = new QVBoxLayout(this);
    cardLayout->setContentsMargins(0, 0, 0, 0);
    cardLayout->setSpacing(kCardSpacingPx);

    // Header: identity symbol, name, and the right slot pushed to the far edge
    // (REQ-UI-SELECTION-CARD).
    m_header = new QWidget(this);
    // The header doubles as the panel's drag handle (REQ-UI-SELECTION-PANEL-DRAG); the
    // cursor says so, as the controls panel's heading does for the click that collapses
    // it. The gesture itself belongs to SelectionPanel, which owns the placement.
    m_header->setCursor(Qt::SizeAllCursor);
    QHBoxLayout* headerLayout = new QHBoxLayout(m_header);
    headerLayout->setContentsMargins(0, 0, 0, 0);
    headerLayout->setSpacing(kHeaderSpacingPx);

    m_symbolLabel = new QLabel(m_header);
    m_symbolLabel->hide();

    m_nameLabel = new QLabel(m_header);
    QFont nameFont = m_nameLabel->font();
    nameFont.setBold(true);
    m_nameLabel->setFont(nameFont);

    m_statusPill = new StatusPill(m_header);

    headerLayout->addWidget(m_symbolLabel);
    headerLayout->addWidget(m_nameLabel);
    headerLayout->addStretch(1);
    headerLayout->addWidget(m_statusPill);
    cardLayout->addWidget(m_header);

    m_configurationGroup = new QWidget(this);
    QVBoxLayout* configurationLayout = new QVBoxLayout(m_configurationGroup);
    configurationLayout->setContentsMargins(0, 0, 0, 0);
    configurationLayout->setSpacing(kCardSpacingPx);
    cardLayout->addWidget(m_configurationGroup);

    m_runtimeGroup = new QWidget(this);
    QVBoxLayout* runtimeLayout = new QVBoxLayout(m_runtimeGroup);
    runtimeLayout->setContentsMargins(0, 0, 0, 0);
    runtimeLayout->setSpacing(kCardSpacingPx);
    cardLayout->addWidget(m_runtimeGroup);

    if (m_siteId.has_value())
    {
        // A site has no buffers and runs no production cycle, so whatever the subclass
        // puts into the runtime group is not shown at all; the construction section
        // takes its place (REQ-BLD-SITE-CONFIG, REQ-UI-SELECTION-CARD). The subclass
        // still fills the group -- it just never becomes visible.
        m_runtimeGroup->hide();

        m_constructionSection = new SectionBox(tr("Construction"), this);
        m_constructionBar = new BarRow(QString(), m_constructionSection);
        m_constructionSection->getContentLayout()->addWidget(m_constructionBar);
        m_constructionSection->getContentLayout()->addWidget(
            new EmptyNote(tr("No buffers until built"), m_constructionSection));
        // Directly below the header rather than where the runtime group sits, so how far
        // along the site is reads before what it is configured to become
        // (REQ-UI-SELECTION-CARD).
        cardLayout->insertWidget(1, m_constructionSection);

        setSlot(QColor(), tr("constructing"));
    }
}

SelectionContent::~SelectionContent() = default;

void SelectionContent::refresh()
{
    refreshConfiguration();
    if (m_siteId.has_value())
    {
        refreshConstruction();
        return;
    }
    refreshRuntime();
}

QVBoxLayout* SelectionContent::getConfigurationLayout()
{
    return static_cast<QVBoxLayout*>(m_configurationGroup->layout());
}

QVBoxLayout* SelectionContent::getRuntimeLayout()
{
    return static_cast<QVBoxLayout*>(m_runtimeGroup->layout());
}

void SelectionContent::setIdentity(const QPixmap& symbol, const QString& name)
{
    m_symbolLabel->setPixmap(symbol);
    m_symbolLabel->setVisible(!symbol.isNull());
    m_nameLabel->setText(name);
}

void SelectionContent::setBuildingIdentity(BuildingType type, const QString& name)
{
    const std::string iconName = buildingTypeId(type);
    setIdentity(m_context.buildingIcons->getChip(iconName, kSymbolSizePx), name);
}

void SelectionContent::setSlot(const QColor& dotColor, const QString& caption)
{
    m_statusPill->setStatus(dotColor, m_context.visuals->statusLight.outline, caption);
}

void SelectionContent::setCountSlot(int count)
{
    setSlot(QColor(), tr("x%1").arg(count));
}

void SelectionContent::clearSlot()
{
    setSlot(QColor(), QString());
}

void SelectionContent::setProductionStatusSlot(const Building& building)
{
    // The classification is the simulation's, so the panel and the world's status light
    // can never disagree (REQ-UI-SELECTION-STATUS, REQ-UI-STATUS-LIGHT).
    const std::optional<ProductionStatus> status =
        getProductionStatus(*m_context.config, building);
    if (!status.has_value())
    {
        clearSlot();
        return;
    }

    const StatusLightVisuals& colors = m_context.visuals->statusLight;
    // The Salvage Bay has no recipe and no cycle: its two states say whether it is
    // holding scrap, not whether it is producing (REQ-BLD-SALVAGE-BAY).
    const bool isSalvageBay = (building.type == BuildingType::SalvageBay);
    switch (*status)
    {
        case ProductionStatus::Unconfigured:
            setSlot(colors.grey, tr("no recipe"));
            break;
        case ProductionStatus::Producing:
            setSlot(colors.green, isSalvageBay ? tr("holding scrap") : tr("producing"));
            break;
        case ProductionStatus::Starved:
            setSlot(colors.red, isSalvageBay ? tr("empty") : tr("missing input"));
            break;
        case ProductionStatus::Blocked:
            setSlot(colors.yellow, tr("output full"));
            break;
    }
}

void SelectionContent::refreshConstruction()
{
    const ConstructionSite* site =
        findSite(m_context.sim->getFactoryState(), *m_siteId);
    if (!site)
    {
        // The site finished or was removed under the card. SelectionPanel rebuilds on
        // the same refresh, so this only has to avoid reading a dead site.
        return;
    }

    if (site->completesAt == 0)
    {
        // Placed but not yet at the head of the construction queue (REQ-BLD-QUEUE), so
        // there is no progress to show yet.
        m_constructionBar->setValue(0.0, tr("Queued"));
        return;
    }

    const BuildingDef* def = m_context.config->buildings.findBuildingDef(site->type);
    if (!def || def->constructionTimeSeconds <= 0)
    {
        m_constructionBar->setValue(0.0, tr("Building..."));
        return;
    }

    const Tick duration = secondsToTicks(def->constructionTimeSeconds);
    const Tick elapsed =
        m_context.sim->getCurrentTick() - (site->completesAt - duration);
    const Tick clamped = std::max(Tick(0), std::min(duration, elapsed));
    const int percent = static_cast<int>(clamped * 100 / duration);
    m_constructionBar->setValue(static_cast<double>(clamped) / duration,
                                tr("%1%").arg(percent));
}
