//
// Abstract game engine interface for MCTS
//

#ifndef EAGLE0_MCTS_GAME_ENGINE_HPP
#define EAGLE0_MCTS_GAME_ENGINE_HPP

#include <chrono>
#include <memory>
#include <vector>

#include "MCTSAction.hpp"
#include "MCTSGameState.hpp"
#include "MCTSTypes.hpp"

namespace shardok {
namespace mcts {

// Information about chance outcomes (supports both binary and multi-outcome)
struct ChanceOutcomeInfo {
    std::vector<double> probabilities;  // Probability of each outcome (must sum to 1.0)
    std::vector<double> rolls;          // Roll values for each outcome

    // Factory for binary success/failure outcomes (e.g., START_FIRE)
    [[nodiscard]] static ChanceOutcomeInfo binary(double successProbability) {
        // -100: triggers open-ended low sequence, succeeds against any threshold
        // 150: triggers open-ended high sequence, fails against any threshold
        return {{successProbability, 1.0 - successProbability}, {-100.0, 150.0}};
    }

    // Factory for multi-outcome with fixed seeds (e.g., END_TURN)
    // Uses uniformly distributed roll values to sample different random outcomes
    [[nodiscard]] static ChanceOutcomeInfo multiOutcome(int numOutcomes) {
        std::vector<double> probs(numOutcomes, 1.0 / numOutcomes);
        std::vector<double> rollValues;
        rollValues.reserve(numOutcomes);
        // Spread rolls across the percentile range: 10, 30, 50, 70, 90 for 5 outcomes
        for (int i = 0; i < numOutcomes; ++i) {
            rollValues.push_back(10.0 + (80.0 * i) / (numOutcomes - 1));
        }
        return {probs, rollValues};
    }

    [[nodiscard]] const std::vector<double>& getRepresentativeRolls() const { return rolls; }

    [[nodiscard]] const std::vector<double>& getProbabilities() const { return probabilities; }
};

// Backward compatibility alias
using BinaryOutcomeInfo = ChanceOutcomeInfo;

// Abstract interface for game engines
class MCTSGameEngine {
public:
    virtual ~MCTSGameEngine() = default;

    // Apply an action to a state and return the resulting state
    // If deterministicRoll is provided (0.0-100.0), use that for any random outcomes
    [[nodiscard]] virtual std::unique_ptr<MCTSGameState> applyAction(
            const MCTSGameState& state,
            const MCTSAction& action,
            double deterministicRoll = -1.0) const = 0;

    // Apply an action to a mutable state in-place (for efficient simulation)
    // Default: clone, apply, and move the result back
    // Override this for better performance
    virtual void applyActionMutable(std::unique_ptr<MCTSGameState>& state, const MCTSAction& action)
            const {
        state = applyAction(*state, action);
    }

    // Get all legal actions for the current state with player flip tracking
    // Default implementation ignores flip tracking and calls base version
    [[nodiscard]] virtual std::vector<std::unique_ptr<MCTSAction>> getLegalActions(
            const MCTSGameState& state,
            MCTSPlayerId /*rootPlayerId*/,
            int /*currentPlayerFlips*/,
            int /*maxPlayerFlips*/) const = 0;

    // Check if a state is terminal
    [[nodiscard]] virtual bool isTerminal(const MCTSGameState& state) const = 0;

    // Evaluate a state from the perspective of a player
    [[nodiscard]] virtual double evaluateState(const MCTSGameState& state, MCTSPlayerId playerId)
            const = 0;

    // Filter actions based on game-specific heuristics
    // Returns indices of actions to keep
    // Default: no filtering (return all indices)
    [[nodiscard]] virtual std::vector<size_t> filterActions(
            const std::vector<std::unique_ptr<MCTSAction>>& actions,
            const MCTSGameState& /*state*/) const {
        std::vector<size_t> indices;
        indices.reserve(actions.size());
        for (size_t i = 0; i < actions.size(); ++i) { indices.push_back(i); }
        return indices;
    }

    // Get heuristic weights for actions (used by WEIGHTED_HEURISTIC simulation policy)
    // Returns weights corresponding to each action (same size as actions vector)
    // Weight of 0.0 = never select, higher = more likely to select
    // Default: uniform weights (all actions equally likely)
    [[nodiscard]] virtual std::vector<double> getActionWeights(
            const std::vector<std::unique_ptr<MCTSAction>>& actions,
            const MCTSGameState& /*state*/) const {
        // Default: uniform weights
        return std::vector<double>(actions.size(), 1.0);
    }

    // Simulate a random playout from the given state
    // Default implementation uses policy to select actions
    [[nodiscard]] virtual double simulateRandomPlayout(
            const MCTSGameState& state,
            MCTSPlayerId playerId,
            int maxDepth,
            MCTSSimulationPolicy policy) const;

    // Get the immediate score of applying an action
    // Default: apply the action and evaluate the resulting state
    [[nodiscard]] virtual double getActionScore(
            const MCTSGameState& state,
            const MCTSAction& action,
            MCTSPlayerId playerId) const {
        auto newState = applyAction(state, action);
        if (!newState) { return 0.0; }
        return evaluateState(*newState, playerId);
    }

    // Check if we should stop searching (e.g., time limit, found winning move)
    [[nodiscard]] virtual bool shouldStopSearch(
            const MCTSGameState& /*state*/,
            int /*iterations*/,
            std::chrono::steady_clock::time_point /*startTime*/) const {
        // Default: no early stopping
        return false;
    }

    // Map a filtered action index back to the original unfiltered index
    // This is needed when getLegalActions() applies filtering - the returned actions
    // may be a subset of all available actions, and this maps back to the original index.
    // Default implementation: no filtering, so filtered index = original index
    [[nodiscard]] virtual size_t mapFilteredIndexToOriginal(
            size_t filteredIndex,
            const MCTSGameState& /*state*/) const {
        // Default: no filtering, index stays the same
        return filteredIndex;
    }

    // Get binary outcome information for an action that requires a chance node
    // Only called for actions where action.requiresChanceNode() returns true
    // Returns success probability for binary success/failure actions
    [[nodiscard]] virtual BinaryOutcomeInfo getBinaryOutcomeInfo(
            const MCTSGameState& state,
            const MCTSAction& action) const = 0;
};

}  // namespace mcts
}  // namespace shardok

#endif  // EAGLE0_MCTS_GAME_ENGINE_HPP
