//
// Fast heuristic weighting implementation with context-aware logic
//

#include "AIHeuristicWeighting.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/ShardokException.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/ActionPointDistances.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"

namespace shardok {

using CommandType = net::eagle0::shardok::common::CommandType;
using Coords = net::eagle0::shardok::storage::fb::Coords;
using ProtoCoords = net::eagle0::shardok::common::Coords;

double AIHeuristicWeighting::GetCommandWeight(
        const CommandType commandType,
        const UnitId actorUnitId,
        const PlayerId actorPlayerId,
        const Coords& targetCoords,
        const GameStateW& state,
        const CoordsSet& castleCoords,
        const APDCache* apdCache,
        bool isDefender,
        std::function<BattalionTypeSPtr(BattalionTypeId)> getBattalionType) {
    // Fast O(1) heuristic weights based on command type and game context
    // Higher weight = more likely to select in simulation
    // 0.0 = never select (filtered out)

    const auto* hexMap = state->hex_map();
    const auto* units = state->units();
    const bool hasTarget = (targetCoords.row() >= 0 && targetCoords.column() >= 0);

    switch (commandType) {
        // === HIGH VALUE OFFENSIVE (10.0) ===
        // Ranged attacks - very valuable, typically available when in range
        case CommandType::ARCHERY_COMMAND: return 20.0;
        case CommandType::LIGHTNING_BOLT_COMMAND: return 10.0;
        case CommandType::FEAR_COMMAND: return 10.0;

        // Area/tactical spells - high impact
        case CommandType::METEOR_START_COMMAND: {
            // METEOR_START doesn't have a target - it's based on actor location
            if (hasTarget) {
                throw ShardokInternalErrorException(
                        "METEOR_START_COMMAND should not have target coordinates");
            }

            // Get actor's location
            const auto* actorUnit = units->Get(actorUnitId);
            if (!actorUnit) {
                throw ShardokInternalErrorException(
                        "METEOR_START_COMMAND actor unit not found in game state");
            }

            const Coords& actorLocation = actorUnit->location();
            int enemyCount = 0;

            // Count enemies within meteor range (3 hexes) of actor location
            constexpr int METEOR_RANGE = 3;
            const auto tilesInRange = TilesWithinDistance(hexMap, actorLocation, METEOR_RANGE);
            for (const auto& tileCoords : tilesInRange) {
                if (const auto* unit = Occupant(units, tileCoords)) {
                    if (unit->player_id() != actorPlayerId) { enemyCount++; }
                }
            }

            return 1.0 + (enemyCount * 15.0);  // Base 1 + 15 per enemy in range
        }

        case CommandType::METEOR_TARGET_COMMAND: {
            // High weight per enemy unit at or adjacent to target
            if (!hasTarget) {
                throw ShardokInternalErrorException(
                        "METEOR_TARGET_COMMAND requires target coordinates for heuristic "
                        "weighting");
            }

            int enemyCount = 0;

            // Count enemies at target
            if (const auto* targetUnit = Occupant(units, targetCoords)) {
                if (targetUnit->player_id() != actorPlayerId) { enemyCount++; }
            }

            // Count enemies adjacent to target
            for (const auto& neighbor : HexMapUtils::GetAdjacentTiles(hexMap, targetCoords)) {
                if (const auto* unit = Occupant(units, neighbor.coords)) {
                    if (unit->player_id() != actorPlayerId) { enemyCount++; }
                }
            }

            return 1.0 + (enemyCount * 15.0);  // Base 1 + 15 per enemy in range
        }

        case CommandType::RAISE_DEAD_COMMAND: return 10.0;
        case CommandType::HOLY_WAVE_COMMAND: return 8.0;

        // Fire on enemy (context-dependent)
        case CommandType::START_FIRE_COMMAND: {
            // High if enemy at target, low otherwise
            if (!hasTarget) {
                throw ShardokInternalErrorException(
                        "START_FIRE_COMMAND requires target coordinates for heuristic weighting");
            }

            if (const auto* targetUnit = Occupant(units, targetCoords)) {
                if (targetUnit->player_id() != actorPlayerId) {
                    return 10.0;  // Enemy at target - high value
                }
            }
            return 1.0;  // No enemy - low value but still valid
        }

        // === MEDIUM-HIGH OFFENSIVE (5.0-7.0) ===
        // Direct damage melee
        case CommandType::MELEE_COMMAND: return 7.0;
        case CommandType::CHARGE_COMMAND: return 7.0;  // Damage + movement
        case CommandType::CHALLENGE_DUEL_COMMAND: return 5.0;

        // Control and tactical magic
        case CommandType::CONTROL_COMMAND: return 6.0;
        case CommandType::METEOR_CAST_COMMAND: return 6.0;  // Finish meteor

        case CommandType::REDUCE_COMMAND: {
            // High if enemy at target, zero otherwise
            if (!hasTarget) return 0.0;

            if (const auto* targetUnit = Occupant(units, targetCoords)) {
                if (targetUnit->player_id() != actorPlayerId) {
                    return 10.0;  // Enemy at target - very high value
                }
            }
            return 0.0;  // No enemy - don't use
        }

        // === MOVEMENT - Context-dependent ===
        case CommandType::MOVE_COMMAND: {
            if (isDefender) {
                return 0.0;  // Defenders don't move
            }

            // Attackers: weight based on distance improvement towards castle
            if (!hasTarget) {
                throw ShardokInternalErrorException(
                        "MOVE_COMMAND requires target coordinates for heuristic weighting");
            }

            // Get actor unit to determine battalion type and start position
            const auto* actorUnit = units->Get(actorUnitId);
            if (!actorUnit) return 4.0;  // Default if can't find actor

            // Get battalion type for distance calculation
            const auto battalionTypeId = actorUnit->battalion().type();
            const auto battalionTypePtr = getBattalionType(battalionTypeId);
            if (!battalionTypePtr) return 4.0;  // Default if can't get battalion type

            // Get ActionPointDistances for this battalion type
            const auto mapId = ActionPointDistancesCache::GetMapId(hexMap);
            const auto* apd = (*apdCache)->GetRaw(hexMap, mapId, battalionTypePtr, false, -1);
            if (!apd) return 4.0;  // Default if can't get distances

            // Calculate minimum distance from start to any castle
            const Coords startCoords = actorUnit->location();
            auto minStartDistance = ActionPointDistances::IMPOSSIBLE;
            for (const auto& castleCoord : castleCoords) {
                const auto dist = apd->Distance(startCoords, castleCoord);
                if (dist < minStartDistance) { minStartDistance = dist; }
            }

            // Calculate minimum distance from end to any castle
            const Coords& endCoords = targetCoords;
            auto minEndDistance = ActionPointDistances::IMPOSSIBLE;
            for (const auto& castleCoord : castleCoords) {
                const auto dist = apd->Distance(endCoords, castleCoord);
                if (dist < minEndDistance) { minEndDistance = dist; }
            }

            // Return weight based on distance improvement
            // Higher weight if we're moving closer to castle
            if (minStartDistance == ActionPointDistances::IMPOSSIBLE ||
                minEndDistance == ActionPointDistances::IMPOSSIBLE) {
                return 4.0;  // Default if distances are impossible
            }

            const auto improvement = static_cast<double>(minStartDistance - minEndDistance);
            return std::max(0.0, improvement);
        }

        case CommandType::BRAVE_WATER_COMMAND: return 3.0;  // Tactical movement
        case CommandType::SCOUT_COMMAND:
            return 2.0;  // Information gathering

        // Terrain manipulation
        case CommandType::FREEZE_WATER_COMMAND: return 3.0;
        case CommandType::BUILD_BRIDGE_COMMAND: return 3.0;

        // === LOW VALUE DEFENSIVE/UTILITY (1.0-2.0) ===
        case CommandType::EXTINGUISH_FIRE_COMMAND: {
            // High if friendly at target, low otherwise
            if (!hasTarget) {
                throw ShardokInternalErrorException(
                        "EXTINGUISH_FIRE_COMMAND requires target coordinates for heuristic "
                        "weighting");
            }

            if (const auto* targetUnit = Occupant(units, targetCoords)) {
                if (targetUnit->player_id() == actorPlayerId) {
                    return 8.0;  // Friendly at target - high value
                }
            }
            return 1.0;  // No friendly - low value but still valid
        }

        case CommandType::UNIT_REST_COMMAND: return 1.5;
        case CommandType::FORTIFY_COMMAND: return 2.0;

        // Zero weight - don't use in simulation
        case CommandType::REPAIR_COMMAND: return 0.0;
        case CommandType::HIDE_COMMAND: return 0.0;
        case CommandType::RELEASE_UNIT_COMMAND: return 0.0;

        case CommandType::REINFORCE_COMMAND: return 10.0;

        // === ZERO WEIGHT - NEVER SELECT (0.0) ===
        // Explicitly bad actions
        case CommandType::FLEE_COMMAND: return 0.0;  // Never flee in simulation
        case CommandType::RETREAT_COMMAND: return 0.0;
        case CommandType::BECOME_OUTLAW_COMMAND: return 0.0;  // Never become outlaw
        case CommandType::DISMISS_UNIT_COMMAND:
            return 0.0;  // Never dismiss in combat

        // Actions that are fine as a fallback
        case CommandType::END_TURN_COMMAND: return 1.0;
        case CommandType::UNIT_STOP_COMMAND: return 1.0;
        case CommandType::METEOR_CANCEL_COMMAND: return 1.0;

        // Setup commands (shouldn't appear in combat, but filter anyway)
        case CommandType::PLACE_UNIT_COMMAND: return 10.0;
        case CommandType::PLACE_HIDDEN_UNIT_COMMAND: return 1.0;
        case CommandType::END_PLAYER_SETUP_COMMAND: return 1.0;

        // Unknown/unhandled
        case CommandType::UNKNOWN_COMMAND:
        default: return 0.0;  // Don't select unknown commands
    }
}

}  // namespace shardok
