"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValidDeathrattleEnchantment = exports.simulateBattle = exports.assignCards = void 0;
const reference_data_1 = require("@firestone-hs/reference-data");
const card_interface_1 = require("./cards/card.interface");
const cards_data_1 = require("./cards/cards-data");
const _card_mappings_1 = require("./cards/impl/_card-mappings");
const input_clone_1 = require("./input-clone");
const input_sanitation_1 = require("./input-sanitation");
const shared_state_1 = require("./simulation/shared-state");
const simulator_1 = require("./simulation/simulator");
const spectator_1 = require("./simulation/spectator/spectator");
let globalCards = new reference_data_1.AllCardsService();
const assignCards = (cards) => {
    globalCards = cards;
};
exports.assignCards = assignCards;
exports.default = async (event) => {
    var _a, _b, _c, _d, _e, _f;
    if (!((_a = event.body) === null || _a === void 0 ? void 0 : _a.length)) {
        console.warn('missing event body', event);
        return;
    }
    const battleInput = JSON.parse(event.body);
    const cards = globalCards;
    await cards.initializeCardsDb();
    const cardsData = new cards_data_1.CardsData(cards, false);
    cardsData.inititialize((_c = (_b = battleInput.gameState) === null || _b === void 0 ? void 0 : _b.validTribes) !== null && _c !== void 0 ? _c : (_d = battleInput.options) === null || _d === void 0 ? void 0 : _d.validTribes, (_f = (_e = battleInput.gameState) === null || _e === void 0 ? void 0 : _e.anomalies) !== null && _f !== void 0 ? _f : []);
    const battleIterator = (0, exports.simulateBattle)(battleInput, cards, cardsData);
    let result = battleIterator.next();
    while (!result.done) {
        result = battleIterator.next();
    }
    const simulationResult = result.value;
    const response = {
        statusCode: 200,
        isBase64Encoded: false,
        body: JSON.stringify(simulationResult),
    };
    return response;
};
const simulateBattle = function* (battleInput, cards, cardsData) {
    var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
    if (!((_a = cards === null || cards === void 0 ? void 0 : cards.getCards()) === null || _a === void 0 ? void 0 : _a.length)) {
        console.error('[simulate-bgs-battle] reference cards are empty, cannot simulate battle', cards);
        return null;
    }
    const start = Date.now();
    const maxAcceptableDuration = ((_b = battleInput.options) === null || _b === void 0 ? void 0 : _b.maxAcceptableDuration) || 8000;
    const hideMaxSimulationDurationWarning = (_d = (_c = battleInput.options) === null || _c === void 0 ? void 0 : _c.hideMaxSimulationDurationWarning) !== null && _d !== void 0 ? _d : false;
    const numberOfSimulations = ((_e = battleInput.options) === null || _e === void 0 ? void 0 : _e.numberOfSimulations) || 8000;
    const intermediateSteps = (_g = (_f = battleInput.options) === null || _f === void 0 ? void 0 : _f.intermediateResults) !== null && _g !== void 0 ? _g : 200;
    const damageConfidence = (_j = (_h = battleInput.options) === null || _h === void 0 ? void 0 : _h.damageConfidence) !== null && _j !== void 0 ? _j : 0.9;
    const includeOutcomeSamples = (_l = (_k = battleInput.options) === null || _k === void 0 ? void 0 : _k.includeOutcomeSamples) !== null && _l !== void 0 ? _l : true;
    const simulationResult = {
        wonLethal: 0,
        won: 0,
        tied: 0,
        lost: 0,
        lostLethal: 0,
        damageWons: [],
        damageWon: 0,
        damageWonRange: null,
        damageLosts: [],
        damageLost: 0,
        damageLostRange: null,
        wonLethalPercent: undefined,
        wonPercent: undefined,
        tiedPercent: undefined,
        lostPercent: undefined,
        lostLethalPercent: undefined,
        averageDamageWon: undefined,
        averageDamageLost: undefined,
    };
    const spectator = new spectator_1.Spectator(includeOutcomeSamples);
    const inputReady = (0, input_sanitation_1.buildFinalInput)(battleInput, cards, cardsData);
    !((_m = battleInput.options) === null || _m === void 0 ? void 0 : _m.skipInfoLogs) && console.time('simulation');
    const outcomes = {};
    for (let i = 0; i < numberOfSimulations; i++) {
        const input = (0, input_clone_1.cloneInput3)(inputReady);
        const inputClone = (0, input_clone_1.cloneInput3)(inputReady);
        const gameState = {
            allCards: cards,
            cardsData: cardsData,
            spectator: spectator,
            sharedState: new shared_state_1.SharedState(),
            currentTurn: input.gameState.currentTurn,
            validTribes: input.gameState.validTribes,
            anomalies: input.gameState.anomalies,
            gameState: {
                player: {
                    player: input.playerBoard.player,
                    board: input.playerBoard.board,
                    teammate: input.playerTeammateBoard,
                },
                opponent: {
                    player: input.opponentBoard.player,
                    board: input.opponentBoard.board,
                    teammate: input.opponentTeammateBoard,
                },
                playerInitial: {
                    player: inputClone.playerBoard.player,
                    board: inputClone.playerBoard.board,
                    teammate: inputClone.playerTeammateBoard,
                },
                opponentInitial: {
                    player: inputClone.opponentBoard.player,
                    board: inputClone.opponentBoard.board,
                    teammate: inputClone.opponentTeammateBoard,
                },
            },
        };
        const simulator = new simulator_1.Simulator(gameState);
        const battleResult = simulator.simulateSingleBattle(gameState.gameState.player, gameState.gameState.opponent);
        if (Date.now() - start > maxAcceptableDuration && !hideMaxSimulationDurationWarning) {
            console.warn('Stopping simulation after', i, 'iterations and ', Date.now() - start, 'ms');
            break;
        }
        if (!battleResult) {
            continue;
        }
        if (battleResult.result === 'won') {
            simulationResult.won++;
            simulationResult.damageWon += battleResult.damageDealt;
            simulationResult.damageWons.push(battleResult.damageDealt);
            if (battleResult.damageDealt >= battleInput.opponentBoard.player.hpLeft) {
                simulationResult.wonLethal++;
            }
        }
        else if (battleResult.result === 'lost') {
            simulationResult.lost++;
            simulationResult.damageLost += battleResult.damageDealt;
            simulationResult.damageLosts.push(battleResult.damageDealt);
            outcomes[battleResult.damageDealt] = ((_o = outcomes[battleResult.damageDealt]) !== null && _o !== void 0 ? _o : 0) + 1;
            if (battleInput.playerBoard.player.hpLeft &&
                battleResult.damageDealt >= battleInput.playerBoard.player.hpLeft) {
                simulationResult.lostLethal++;
            }
        }
        else if (battleResult.result === 'tied') {
            simulationResult.tied++;
        }
        spectator.commitBattleResult(battleResult.result);
        if (!!intermediateSteps && i > 0 && i % intermediateSteps === 0) {
            updateSimulationResult(simulationResult, inputReady, damageConfidence);
            yield simulationResult;
        }
    }
    updateSimulationResult(simulationResult, inputReady, damageConfidence);
    !((_p = battleInput.options) === null || _p === void 0 ? void 0 : _p.skipInfoLogs) && console.timeEnd('simulation');
    spectator.prune();
    simulationResult.outcomeSamples = spectator.buildOutcomeSamples(battleInput.gameState);
    simulationResult.damageWons = [];
    simulationResult.damageLosts = [];
    return simulationResult;
};
exports.simulateBattle = simulateBattle;
const updateSimulationResult = (simulationResult, input, damageConfidence) => {
    const totalMatches = simulationResult.won + simulationResult.tied + simulationResult.lost;
    simulationResult.wonPercent = checkRounding(Math.round((10 * (100 * simulationResult.won)) / totalMatches) / 10, simulationResult.won, totalMatches);
    simulationResult.wonLethalPercent = checkRounding(Math.round((10 * (100 * simulationResult.wonLethal)) / totalMatches) / 10, simulationResult.wonLethal, totalMatches);
    simulationResult.lostPercent = checkRounding(Math.round((10 * (100 * simulationResult.lost)) / totalMatches) / 10, simulationResult.lost, totalMatches);
    simulationResult.lostLethalPercent = checkRounding(Math.round((10 * (100 * simulationResult.lostLethal)) / totalMatches) / 10, simulationResult.lostLethal, totalMatches);
    simulationResult.tiedPercent = checkRounding(Math.max(0, 100 - simulationResult.lostPercent - simulationResult.wonPercent), simulationResult.tied, totalMatches);
    const totalDamageWon = simulationResult.damageWons.reduce((a, b) => a + b, 0);
    const totalDamageLost = simulationResult.damageLosts.reduce((a, b) => a + b, 0);
    const damageWonRange = calculateDamageRange(simulationResult.damageWons, damageConfidence);
    const damageLostRange = calculateDamageRange(simulationResult.damageLosts, damageConfidence);
    simulationResult.averageDamageWon = simulationResult.won ? totalDamageWon / simulationResult.won : 0;
    simulationResult.averageDamageLost = simulationResult.lost ? totalDamageLost / simulationResult.lost : 0;
    simulationResult.damageWonRange = simulationResult.won ? damageWonRange : null;
    simulationResult.damageLostRange = simulationResult.lost ? damageLostRange : null;
    if (simulationResult.averageDamageWon > 0 &&
        simulationResult.averageDamageWon < input.playerBoard.player.tavernTier) {
        console.warn('average damage won issue');
    }
    if (simulationResult.averageDamageLost > 0 &&
        simulationResult.averageDamageLost < input.opponentBoard.player.tavernTier) {
        console.warn('average damage lost issue');
    }
};
const calculateDamageRange = (damageArray, damageConfidence) => {
    if (damageArray.length === 0) {
        return { min: 0, max: 0 };
    }
    const sortedDamage = [...damageArray].sort((a, b) => a - b);
    const percentile = (arr, p) => {
        const index = Math.floor(p * arr.length);
        return arr[index];
    };
    const minDamage = percentile(sortedDamage, 1 - damageConfidence);
    const maxDamage = percentile(sortedDamage, damageConfidence);
    return { min: minDamage, max: maxDamage };
};
const checkRounding = (roundedValue, initialValue, totalValue) => {
    if (roundedValue === 0 && initialValue !== 0) {
        return 0.01;
    }
    if (roundedValue === 100 && initialValue !== totalValue) {
        return 99.9;
    }
    return roundedValue;
};
const VALID_DEATHRATTLE_ENCHANTMENTS = [
    "BG_BOT_312e",
    "TB_BaconUps_032e",
    "UNG_999t2e",
    "BG21_000e",
    "BG21_000_Ge",
    "BG21_HERO_030p",
    "BG21_HERO_030pe",
    "BG22_HERO_001_Buddy_e1",
    "BG22_HERO_001_Buddy_e2",
    "BG22_HERO_001_Buddy_e4",
    "BG22_HERO_001_Buddy_e3",
    "BG22_HERO_001p_t1e",
    "BG22_HERO_001p_t4_s",
    "BG28_603e",
    "BG30_MagicItem_917e",
    "BG30_MagicItem_411e",
    "BG30_MagicItem_952e",
    "BG31_HERO_801ptee",
    "BG32_172e",
    "BG32_172_Ge",
    "BG20_HERO_282e2",
];
const validDeathrattleEnchantmentsFromMapping = [];
const isValidDeathrattleEnchantment = (cardId) => {
    if (VALID_DEATHRATTLE_ENCHANTMENTS.includes(cardId)) {
        return true;
    }
    if (validDeathrattleEnchantmentsFromMapping.length === 0) {
        for (const cardImpl of Object.values(_card_mappings_1.cardMappings)) {
            if ((0, card_interface_1.hasDeathrattleSpawnEnchantment)(cardImpl)) {
                validDeathrattleEnchantmentsFromMapping.push(...cardImpl.cardIds);
            }
        }
    }
    return validDeathrattleEnchantmentsFromMapping.includes(cardId);
};
exports.isValidDeathrattleEnchantment = isValidDeathrattleEnchantment;
//# sourceMappingURL=simulate-bgs-battle.js.map