"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.hasVariationActionsOnView = exports.selectExperiment = void 0;
const bucketing_1 = require("./bucketing");
const condition_1 = __importDefault(require("../lib/condition"));
/**
 * Decide which experiment the visitor should be bucketed into. Experiment IDs not included in
 * experimentPriorities are implicitly ignored.
 * @param {Layer} layer - Layer from decisionTicket
 * @param {string[]} audienceIds - Visitor's audienceIds from decisionTicket
 * @param {string} bucketingId - BucketingId from decisionTicket
 * @param {number} priorityLevel - Which experiment priority level to check
 * @param {experimentPriorities} layer.decisionMetadata.experimentPriorities Array of arrays showing experiment priorities (and optionally weight distributions)
 * @returns {Object} An object with the selected experiment
 * @private
 */
async function selectExperiment(layer, audienceIds, bucketingId, priorityLevel, logger) {
    var candidatesInPriorityLevel = layer.decisionMetadata.experimentPriorities[priorityLevel];
    var selectedExperimentId;
    var filterCandidates = function (res, item) {
        var experiment = getExperimentById(layer, item.entityId);
        if (isValidExperiment(audienceIds, experiment, logger)) {
            res.push(item.entityId);
        }
        return res;
    };
    var candidates = candidatesInPriorityLevel.reduce(filterCandidates, []);
    // If any experiment in priority level matches audiences, bucket visitor
    if (candidates.length > 0) {
        var normalizedDistributions;
        var nullWeights = candidatesInPriorityLevel.filter(candidate => candidate.endOfRange === null);
        if (nullWeights &&
            nullWeights.length === candidatesInPriorityLevel.length) {
            normalizedDistributions = getEqualWeights(candidates);
        }
        else if (nullWeights.length === 0) {
            normalizedDistributions = getNormalizedDistributions(candidates, candidatesInPriorityLevel);
        }
        else {
            throw new DecisionError('You must specify weights for all or none of the experiments in each priority layer.');
        }
        selectedExperimentId = (0, bucketing_1.chooseWeightedCandidate)(bucketingId, layer.id, normalizedDistributions);
        if (selectedExperimentId) {
            var selectedExperiment = getExperimentById(layer, selectedExperimentId);
            if (selectedExperiment) {
                return selectedExperiment;
            }
        }
    }
    return null;
}
exports.selectExperiment = selectExperiment;
/**
 * Get experiment given its id
 * @param {Layer} layer
 * @param {string} experimentId
 * @returns {Experiment}
 */
function getExperimentById(layer, experimentId) {
    var selectedExperiment = layer.experiments.find(experiment => experiment.id === experimentId);
    if (selectedExperiment) {
        return selectedExperiment;
    }
    throw new DecisionError('Unable to find selected experiment.');
}
/**
 * Evaluate whether the visitor's audienceIds satisfy the experiment's audienceId conditions
 * @param {string[]} audienceIds ids of audiences that the visitor satisfies
 * @param {Experiment} experiment
 * @returns {boolean}
 */
function isValidExperiment(audienceIds, experiment, logger) {
    var evaluator = audienceId => audienceIds.includes(audienceId);
    var result;
    logger.debug('Decision / Evaluating audiences for experiment:', experiment, audienceIds);
    if (!experiment.audienceIds) {
        result = true;
    }
    else {
        result = condition_1.default.evaluate(experiment.audienceIds, evaluator);
    }
    logger.debug('Decision / Experiment', experiment.id, 'is valid?', result);
    return result;
}
/**
 * Utility function to create equal weight distributions for priority layers where weights are not specified.
 * @private
 * @param {Array.<string>} experimentIds of experimentIds where visitor matches at least one audience
 * @returns {WeightDistribution} array of weight distributions for the experiments
 * @private
 */
function getEqualWeights(experimentIds) {
    var distributions = [];
    var intervals = Math.round(10000 / experimentIds.length);
    for (var k = 0; k < experimentIds.length; k++) {
        distributions.push({
            entityId: experimentIds[k],
            endOfRange: intervals * (k + 1),
        });
    }
    // Make sure distributions ends at 10000 (TODO: This is bad if there are a lot of entities)
    distributions[distributions.length - 1].endOfRange = 10000;
    return distributions;
}
/**
 * Utility function to create the weight distributions for matching experiments in a priority layer
 * @private
 * @param {Array.<string>} experimentIds of experimentIds where visitor matches at least one audience
 * @param {Array.<WeightDistribution>} weightDistributions describing the weight distributions for a priority level
 * @returns {WeightDistribution} array of weight distributions for the experiments
 * @private
 */
function getNormalizedDistributions(experimentIds, weightDistributions) {
    var distributions = [];
    var values = [];
    var total = 0;
    // Get the new "total" points existing
    for (var i = 0; i < weightDistributions.length; i++) {
        var points = 0;
        if (experimentIds.indexOf(weightDistributions[i].entityId) >= 0) {
            if (i === 0) {
                points = weightDistributions[0].endOfRange;
            }
            else {
                points =
                    weightDistributions[i].endOfRange -
                        weightDistributions[i - 1].endOfRange;
            }
            total += points;
            values.push({
                entityId: weightDistributions[i].entityId,
                points: points,
            });
        }
    }
    // Normalize weight distributions
    if (_.isEmpty(distributions)) {
        var lastEndOfRange = 0;
        for (var j = 0; j < values.length; j++) {
            var newEndOfRange = Math.round((values[j].points * 10000) / total) + lastEndOfRange;
            distributions.push({
                entityId: values[j].entityId,
                endOfRange: newEndOfRange,
            });
            lastEndOfRange = newEndOfRange;
        }
    }
    // Make sure distributions ends at 10000 (TODO: This is bad if there are a lot of entities)
    distributions[distributions.length - 1].endOfRange = 10000;
    return distributions;
}
/**
 * Helper function to determine whether the selected variation has actions on the current view
 * @param {Variation} variation
 * @param {string} currentPageId
 * @returns {boolean}
 */
function hasVariationActionsOnView(variation, currentPageId) {
    Logger.debug('Decision / Checking variation:', variation, 'for actions on pageId:', currentPageId);
    return !!_.find(variation.actions, function (action) {
        return action.viewId === currentPageId && !_.isEmpty(action.changes);
    });
}
exports.hasVariationActionsOnView = hasVariationActionsOnView;
