#include "FieldSelectionPanel.h"

#include <algorithm>
#include <map>
#include <string>

#include <QFont>
#include <QLabel>
#include <QStringList>
#include <QVBoxLayout>

#include "DebrisSystem.h"
#include "DisplayName.h"
#include "EntityAdmin.h"
#include "FactionComponent.h"
#include "GameConfig.h"
#include "HealthComponent.h"
#include "ModuleOwnerComponent.h"
#include "SelectedBehaviorComponent.h"
#include "ShipIdentityComponent.h"
#include "ShipStatsCalculator.h"
#include "ShipStatsPanel.h"
#include "Simulation.h"
#include "StationBodyComponent.h"
#include "ThreatCostCalculator.h"
#include "WeaponComponent.h"


FieldSelectionPanel::FieldSelectionPanel(Simulation* sim,
                                         const GameConfig* config,
                                         QWidget* parent)
    : QWidget(parent)
    , m_sim(sim)
    , m_config(config)
{
    // Zero margins and the same spacing as the enclosing SelectedBuildingPanel layout, so
    // nesting the field widgets in this panel leaves their geometry unchanged.
    m_layout = new QVBoxLayout(this);
    m_layout->setContentsMargins(0, 0, 0, 0);
    m_layout->setSpacing(4);
    m_layout->setAlignment(Qt::AlignTop);

    m_entityTitleLabel = new QLabel(this);
    QFont titleFont = m_entityTitleLabel->font();
    titleFont.setBold(true);
    m_entityTitleLabel->setFont(titleFont);
    m_layout->addWidget(m_entityTitleLabel);
    m_entityTitleLabel->hide();

    m_entityStatsPanel = new ShipStatsPanel(config, this);
    m_layout->addWidget(m_entityStatsPanel);
    m_entityStatsPanel->hide();

    m_stationStatsLabel = new QLabel(this);
    m_stationStatsLabel->setWordWrap(true);
    m_layout->addWidget(m_stationStatsLabel);
    m_stationStatsLabel->hide();

    m_entitySummaryLabel = new QLabel(this);
    m_entitySummaryLabel->setWordWrap(true);
    m_layout->addWidget(m_entitySummaryLabel);
    m_entitySummaryLabel->hide();

    m_scrapLabel = new QLabel(this);
    m_layout->addWidget(m_scrapLabel);
    m_scrapLabel->hide();

    hide();

    registerForEvents();
}

FieldSelectionPanel::~FieldSelectionPanel()
{
    unregisterForEvents();
}

void FieldSelectionPanel::setSelectedEntities(const std::vector<entt::entity>& entities)
{
    m_selectedEntities = entities;
    rebuild();
}

void FieldSelectionPanel::setSelectedDebris(const std::vector<entt::entity>& debris)
{
    m_selectedDebris = debris;
    rebuild();
}

void FieldSelectionPanel::clearSelection()
{
    m_selectedEntities.clear();
    m_selectedDebris.clear();
    rebuild();
}

bool FieldSelectionPanel::hasSelection() const
{
    return !m_selectedEntities.empty() || !m_selectedDebris.empty();
}

void FieldSelectionPanel::hideAllWidgets()
{
    m_entityTitleLabel->hide();
    m_entityStatsPanel->hide();
    m_stationStatsLabel->hide();
    m_entitySummaryLabel->hide();
    m_scrapLabel->hide();
}

void FieldSelectionPanel::rebuild()
{
    if (!hasSelection())
    {
        // Nothing in the field category: take no space, leaving the panel to whatever
        // the building category shows (REQ-UI-SELECTION-CATEGORIES).
        hideAllWidgets();
        hide();
        return;
    }

    show();

    EntityAdmin& admin = m_sim->getAdmin();

    // A full single-object stats panel is shown only for a lone field object: one actor
    // with no debris, or one piece of debris with no actors. As soon as the selection holds
    // more than one object (multiple actors, multiple debris, or actors plus debris), the
    // panel switches to the compact count summary (REQ-UI-FIELD-MULTI-SELECTION).
    if (m_selectedEntities.size() == 1 && m_selectedDebris.empty())
    {
        m_entitySummaryLabel->hide();
        m_scrapLabel->hide();
        const entt::entity entity = m_selectedEntities.front();
        if (admin.isValid(entity) && admin.hasAll<ShipIdentityComponent>(entity))
        {
            buildEntityShip(entity);
        }
        else if (admin.isValid(entity) && admin.hasAll<StationBodyComponent>(entity))
        {
            buildEntityStation(entity);
        }
        else
        {
            m_entityTitleLabel->hide();
            m_entityStatsPanel->hide();
            m_stationStatsLabel->hide();
        }
        return;
    }

    if (m_selectedEntities.empty() && m_selectedDebris.size() == 1)
    {
        // Single piece of debris: a "Debris" heading plus a "Scrap" stat row, styled like
        // the ship/station stats panels (REQ-UI-DEBRIS-PANEL).
        m_entitySummaryLabel->hide();
        m_entityStatsPanel->hide();
        m_stationStatsLabel->hide();
        buildDebrisSingle();
        return;
    }

    // More than one field object: a compact count summary. buildEntitySummary() appends the
    // "Debris x N" and "Scrap x N" lines when debris is part of the selection.
    m_entityTitleLabel->hide();
    m_entityStatsPanel->hide();
    m_stationStatsLabel->hide();
    m_scrapLabel->hide();
    buildEntitySummary();
}

void FieldSelectionPanel::refreshDisplay()
{
    if (!hasSelection()) { return; }

    // Keep the live values current: the single-actor stats panel, the single-debris stats
    // panel (whose Scrap row shrinks as it is collected), or the count summary (whose Scrap
    // line shrinks likewise) — matching the layout chosen by rebuild()
    // (REQ-UI-SHIP-STATS-PANEL, REQ-UI-DEBRIS-PANEL).
    if (m_selectedEntities.size() == 1 && m_selectedDebris.empty())
    {
        refreshEntityStats();
    }
    else if (m_selectedEntities.empty() && m_selectedDebris.size() == 1)
    {
        buildDebrisSingle();
    }
    else
    {
        buildEntitySummary();
    }
}

void FieldSelectionPanel::buildDebrisSingle()
{
    // "Debris" heading + a single "Scrap" stat row for the piece's remaining amount,
    // mirroring the single-actor stats panels (REQ-UI-DEBRIS-PANEL).
    m_entityTitleLabel->setText(tr("Debris"));
    m_entityTitleLabel->show();
    m_scrapLabel->setText(tr("Scrap: %1").arg(selectedDebrisScrapTotal()));
    m_scrapLabel->show();
}

void FieldSelectionPanel::buildEntitySummary()
{
    EntityAdmin& admin = m_sim->getAdmin();

    // Group actors by faction + kind + ship schematic, preserving first-seen order
    // (REQ-UI-FIELD-MULTI-SELECTION).
    std::vector<QString> keys;
    std::map<QString, int> counts;
    std::map<QString, QString> labels;

    for (entt::entity entity : m_selectedEntities)
    {
        if (!admin.isValid(entity)) { continue; }
        const bool isEnemy = admin.hasAll<FactionComponent>(entity)
            && admin.get<FactionComponent>(entity).isEnemy;

        QString key;
        QString label;
        if (admin.hasAll<ShipIdentityComponent>(entity))
        {
            const std::string& id = admin.get<ShipIdentityComponent>(entity).schematicId;
            const QString name = QString::fromStdString(toDisplayName(id));
            key = (isEnemy ? QStringLiteral("ship:enemy:") : QStringLiteral("ship:player:"))
                + QString::fromStdString(id);
            label = isEnemy ? tr("Enemy %1").arg(name) : name;
        }
        else if (admin.hasAll<StationBodyComponent>(entity))
        {
            key = isEnemy ? QStringLiteral("station:enemy") : QStringLiteral("station:player");
            label = isEnemy ? tr("Enemy Defence Station") : tr("Player Defence Station");
        }
        else
        {
            continue;
        }

        if (counts.find(key) == counts.end())
        {
            keys.push_back(key);
            labels[key] = label;
        }
        counts[key] += 1;
    }

    // One "<type> x <count>" line per group (matching the recipe tooltip and the building
    // multi-selection). No total-count header, consistent with the building panel. When
    // debris is part of the selection, a "Debris x <count>" line followed by a
    // "Scrap x <total>" line are appended into the same label so the line spacing is
    // uniform (REQ-UI-FIELD-MULTI-SELECTION, REQ-UI-DEBRIS-PANEL).
    QStringList lines;
    for (const QString& key : keys)
    {
        lines << tr("%1 x %2").arg(labels[key]).arg(counts[key]);
    }
    if (!m_selectedDebris.empty())
    {
        lines << tr("Debris x %1").arg(static_cast<int>(m_selectedDebris.size()));
        lines << scrapTotalText();
    }
    m_entitySummaryLabel->setText(lines.join('\n'));
    m_entitySummaryLabel->show();
}

void FieldSelectionPanel::buildEntityShip(entt::entity entity)
{
    EntityAdmin& admin = m_sim->getAdmin();
    const ShipIdentityComponent& identity = admin.get<ShipIdentityComponent>(entity);
    const HealthComponent& health = admin.get<HealthComponent>(entity);

    m_entityTitleLabel->setText(tr("Ship: %1")
        .arg(QString::fromStdString(identity.schematicId)));
    m_entityTitleLabel->show();

    const ShipStats stats = buildShipStatsFromEntity(admin, entity);
    m_entityStatsPanel->refreshFromLive(stats, health.hp);
    m_entityStatsPanel->setBehavior(
        admin.get<SelectedBehaviorComponent>(entity).winner);
    m_entityStatsPanel->setDebugDrawEnabled(m_debugDraw);

    const ShipDef* schematicDef =
        m_config->ships.findShipDef(identity.schematicId);
    if (schematicDef)
    {
        const double threat = calculateShipThreatCost(
            m_config->threatCosts, *m_config, schematicDef->id,
            schematicDef->defaultModules);
        m_entityStatsPanel->setThreatCost(threat);
    }

    m_entityStatsPanel->show();

    m_stationStatsLabel->hide();
}

void FieldSelectionPanel::buildEntityStation(entt::entity entity)
{
    EntityAdmin& admin = m_sim->getAdmin();
    const HealthComponent& health = admin.get<HealthComponent>(entity);

    const bool isEnemy = admin.hasAll<FactionComponent>(entity)
        && admin.get<FactionComponent>(entity).isEnemy;
    m_entityTitleLabel->setText(isEnemy
        ? tr("Enemy Defence Station")
        : tr("Player Defence Station"));
    m_entityTitleLabel->show();

    float totalDps = 0.0f;
    float maxRange = 0.0f;
    bool hasWeapons = false;

    admin.forEach<ModuleOwnerComponent, WeaponComponent>(
        [&](entt::entity /*child*/, const ModuleOwnerComponent& owner, const WeaponComponent& w)
        {
            if (owner.owner != entity) { return; }
            hasWeapons = true;
            totalDps += w.damage * w.fireRateHz;
            if (w.range_tiles > maxRange) { maxRange = w.range_tiles; }
        });

    QString statsText = tr("HP: %1 / %2")
        .arg(static_cast<int>(health.hp + 0.5f))
        .arg(static_cast<int>(health.maxHp + 0.5f));

    if (hasWeapons)
    {
        statsText += tr("\nDPS: %1").arg(QString::number(static_cast<double>(totalDps), 'f', 1));
        statsText += tr("\nRange: %1 tiles").arg(QString::number(static_cast<double>(maxRange), 'f', 1));
    }

    m_stationStatsLabel->setText(statsText);
    m_stationStatsLabel->show();

    m_entityStatsPanel->hide();
}

void FieldSelectionPanel::refreshEntityStats()
{
    // Only the single-actor stats panel needs a live refresh; the multi-actor summary is
    // static counts, and GameWorldView prunes dead/despawned actors and re-emits the
    // selection (REQ-UI-ENTITY-CLICK-SELECT), so the panel does not mutate it here.
    if (m_selectedEntities.size() != 1) { return; }

    EntityAdmin& admin = m_sim->getAdmin();
    const entt::entity entity = m_selectedEntities.front();

    if (!admin.isValid(entity) || !admin.hasAll<HealthComponent>(entity)) { return; }
    const HealthComponent& health = admin.get<HealthComponent>(entity);
    if (health.hp <= 0.0f) { return; }

    if (admin.hasAll<ShipIdentityComponent>(entity))
    {
        const ShipStats stats = buildShipStatsFromEntity(admin, entity);
        m_entityStatsPanel->refreshFromLive(stats, health.hp);
        m_entityStatsPanel->setBehavior(
            admin.get<SelectedBehaviorComponent>(entity).winner);
    }
    else if (admin.hasAll<StationBodyComponent>(entity))
    {
        buildEntityStation(entity);
    }
}

int FieldSelectionPanel::selectedDebrisScrapTotal() const
{
    // Sum the remaining scrap across the still-living selected debris (REQ-UI-DEBRIS-PANEL).
    int total = 0;
    for (const DebrisInfo& info : m_sim->getDebrisSystem().getAllDebrisInfo())
    {
        if (std::find(m_selectedDebris.begin(), m_selectedDebris.end(), info.entity)
            != m_selectedDebris.end())
        {
            total += info.amount;
        }
    }
    return total;
}

QString FieldSelectionPanel::scrapTotalText() const
{
    return tr("Scrap x %1").arg(selectedDebrisScrapTotal());
}

void FieldSelectionPanel::handleEvent(std::shared_ptr<const TickAdvancedEvent> /*event*/)
{
    refreshDisplay();
}

void FieldSelectionPanel::handleEvent(
    std::shared_ptr<const PlayerCommandsAppliedEvent> /*event*/)
{
    // Player commands are applied by a queued drain, not synchronously. When the game is
    // paused no tick advances, so TickAdvancedEvent never fires; refresh here too.
    refreshDisplay();
}

void FieldSelectionPanel::handleEvent(std::shared_ptr<const DebugDrawToggledEvent> event)
{
    m_debugDraw = event->active;
    m_entityStatsPanel->setDebugDrawEnabled(event->active);
}
