//
// Shardok-specific game engine adapter implementation
//

#include "ShardokGameEngine.hpp"

#include "ShardokAction.hpp"
#include "ShardokGameState.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AICommandFilter.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIScoreCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings/GameSettings.hpp"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-redundant-constexpr-static-def"
#include "src/main/protobuf/net/eagle0/shardok/common/command_type.pb.h"
#pragma clang diagnostic pop

namespace shardok::mcts {

// Static helper for average random generator
// Note: Using nullptr for simplicity - could be improved with proper random generator

ShardokGameEngine::ShardokGameEngine(
        [[maybe_unused]] const ShardokEngine* engine,
        const AICommandFilter* commandFilter,
        const AIScoreCalculator* scoreCalculator,
        const GameSettingsSPtr& gameSettings,
        const APDCache* apdCache,
        const ALCache* alCache,
        PlayerId playerId,
        bool isDefender,
        const AIStrategy& strategy,
        const CoordsSet& castleCoords,
        const CoordsSet& criticalTileCoords)
    : commandFilter_(commandFilter),
      scoreCalculator_(scoreCalculator),
      gameSettings_(gameSettings),
      apdCache_(apdCache),
      alCache_(alCache),
      playerId_(playerId),
      isDefender_(isDefender),
      strategy_(strategy),
      castleCoords_(castleCoords),
      criticalTileCoords_(criticalTileCoords) {}

std::unique_ptr<MCTSGameState> ShardokGameEngine::applyAction(
        const MCTSGameState& state,
        const MCTSAction& action) const {
    const auto* shardokState = dynamic_cast<const ShardokGameState*>(&state);
    const auto* shardokAction = dynamic_cast<const ShardokAction*>(&action);

    if (!shardokState || !shardokAction) { return nullptr; }

    const auto currentPlayer = static_cast<PlayerId>(state.currentPlayerId());

    // Use cached engine if available (avoids recomputing GetAvailableCommands for same state)
    std::shared_ptr<ShardokEngine> engine;
    if (auto cachedEngine = shardokState->getCachedEngine()) {
        // Clone the cached engine to preserve command cache
        engine = std::make_shared<ShardokEngine>(*cachedEngine);
    } else {
        // Create fresh engine and populate command cache
        engine = std::make_shared<ShardokEngine>(
                gameSettings_,
                shardokState->getShardokState(),
                criticalTileCoords_,
                0,
                false);
        // Populate command cache (result intentionally unused, just populating cache)
        [[maybe_unused]] const auto commands =
                engine->GetAvailableCommandsForAIPlayer(currentPlayer);
        // Cache the engine for future use with this state
        shardokState->setCachedEngine(engine);
        // Clone it for applying the action (don't mutate the cached engine)
        engine = std::make_shared<ShardokEngine>(*engine);
    }

    engine->PostCommand(currentPlayer, shardokAction->getIndex(), nullptr);

    // Create and return the new state (don't cache the mutated engine)
    return std::make_unique<ShardokGameState>(
            engine->GetCurrentGameState(),
            scoreCalculator_,
            gameSettings_.get(),
            isDefender_,
            strategy_,
            castleCoords_,
            *apdCache_,
            *alCache_,
            criticalTileCoords_);
}

void ShardokGameEngine::applyActionMutable(
        std::unique_ptr<MCTSGameState>& state,
        const MCTSAction& action) const {
    auto* shardokState = dynamic_cast<ShardokGameState*>(state.get());
    const auto* shardokAction = dynamic_cast<const ShardokAction*>(&action);

    if (!shardokState || !shardokAction) {
        // Fallback to default implementation
        state = applyAction(*state, action);
        return;
    }

    const auto currentPlayer = static_cast<PlayerId>(state->currentPlayerId());

    // Use cached engine if available
    std::shared_ptr<ShardokEngine> engine;
    if (auto cachedEngine = shardokState->getCachedEngine()) {
        engine = std::make_shared<ShardokEngine>(*cachedEngine);
    } else {
        engine = std::make_shared<ShardokEngine>(
                gameSettings_,
                shardokState->getShardokState(),
                criticalTileCoords_,
                0,
                false);
        // Populate command cache (result intentionally unused, just populating cache)
        [[maybe_unused]] const auto commands =
                engine->GetAvailableCommandsForAIPlayer(currentPlayer);
        shardokState->setCachedEngine(engine);
        engine = std::make_shared<ShardokEngine>(*engine);
    }

    engine->PostCommand(currentPlayer, shardokAction->getIndex(), nullptr);
    shardokState->getMutableShardokState() = engine->GetCurrentGameState();
    // Clear the cached engine since the state has been mutated
    shardokState->setCachedEngine(nullptr);
}

std::vector<std::unique_ptr<MCTSAction>> ShardokGameEngine::getLegalActions(
        const MCTSGameState& state) const {
    const auto* shardokState = dynamic_cast<const ShardokGameState*>(&state);
    if (!shardokState) { return {}; }

    const auto currentPlayer = static_cast<PlayerId>(state.currentPlayerId());

    // Stop simulation if it's not our player's turn (turn boundary)
    if (currentPlayer != playerId_) { return {}; }

    // Use cached engine if available, otherwise create and cache it
    std::shared_ptr<ShardokEngine> engine;
    if (auto cachedEngine = shardokState->getCachedEngine()) {
        engine = cachedEngine;
    } else {
        engine = std::make_shared<ShardokEngine>(
                gameSettings_,
                shardokState->getShardokState(),
                criticalTileCoords_);
        shardokState->setCachedEngine(engine);
    }

    const CommandListSPtr commands = engine->GetAvailableCommandsForAIPlayer(currentPlayer);

    if (!commands || commands->empty()) { return {}; }

    // Filter commands using AICommandFilter (matching original MCTSAI behavior)
    // Use gameSettings for battalion type lookups
    const std::vector<size_t> filteredIndices = AICommandFilter::FilterCommands(
            commands,
            currentPlayer,
            isDefender_,
            shardokState->getShardokState(),
            *apdCache_,
            [this](BattalionTypeId typeId) {
                return gameSettings_->GetGetter().GetBattalionType(typeId);
            });

    // Convert only filtered commands to MCTSActions
    std::vector<std::unique_ptr<MCTSAction>> actions;
    actions.reserve(filteredIndices.size());

    for (const size_t idx : filteredIndices) {
        if (idx < commands->size()) {
            const auto& cmd = commands->at(idx);
            actions.push_back(std::make_unique<ShardokAction>(cmd->GetCommandProto(), idx));
        }
    }

    return actions;
}

bool ShardokGameEngine::isTerminal(const MCTSGameState& state) const { return state.isTerminal(); }

double ShardokGameEngine::evaluateState(const MCTSGameState& state, MCTSPlayerId playerId) const {
    return state.score(playerId);
}

std::vector<size_t> ShardokGameEngine::filterActions(
        const std::vector<std::unique_ptr<MCTSAction>>& actions,
        const MCTSGameState& state) const {
    const auto* shardokState = dynamic_cast<const ShardokGameState*>(&state);
    if (!shardokState || !commandFilter_) {
        // No filtering - return all indices
        std::vector<size_t> indices;
        indices.reserve(actions.size());
        for (size_t i = 0; i < actions.size(); ++i) { indices.push_back(i); }
        return indices;
    }

    // For now, return all indices - proper filtering would need more work
    // to match the AICommandFilter::FilterCommands signature
    std::vector<size_t> indices;
    indices.reserve(actions.size());
    for (size_t i = 0; i < actions.size(); ++i) { indices.push_back(i); }
    return indices;
}

double ShardokGameEngine::getActionScore(
        const MCTSGameState& state,
        const MCTSAction& action,
        MCTSPlayerId playerId) const {
    auto newState = applyAction(state, action);
    if (!newState) { return 0.0; }
    return newState->score(playerId);
}

bool ShardokGameEngine::shouldStopSearch(
        const MCTSGameState& /*state*/,
        int /*iterations*/,
        std::chrono::steady_clock::time_point /*startTime*/) const {
    // Could add early termination logic here
    return false;
}

size_t ShardokGameEngine::mapFilteredIndexToOriginal(
        size_t filteredIndex,
        const MCTSGameState& state) const {
    // Get the filtered actions (uses cached engine)
    auto actions = getLegalActions(state);

    // Check bounds
    if (filteredIndex >= actions.size()) { return filteredIndex; }

    // Extract the original index from the ShardokAction
    const auto* shardokAction = dynamic_cast<const ShardokAction*>(actions[filteredIndex].get());
    if (!shardokAction) { return filteredIndex; }

    // ShardokAction stores the original unfiltered index
    return shardokAction->getIndex();
}

}  // namespace shardok::mcts
