Source: choice.js

import { baseGenerator } from './base-generator'
import { uniform } from './uniform'
import { randint } from './randint'

function chooseOneWeighted(array, weights, totalWeight) {
    const n = array.length
    const threshold = uniform(0.0, totalWeight)
    let accum = 0
    for (let i = 0; i < n; i++) {
        accum += weights[i]
        if (threshold < accum) {
            return array[i]
        }
    }

    // Fallback for floating precision
    return array[array.length - 1]
}

function chooseOneUniform(array) {
    return array[randint(array.length)]
}


/**
 * Shuffles the elements of an array along the first axis in-place.
 *
 * For N-dimensional arrays, this function only shuffles the order of the top-level sub-arrays.
 * The content and order within each sub-array remain unchanged.
 *
 * @param {Array} array - The array to shuffle. Must be N-dimensional.
 * @returns {Array} A new shuffled array if a deep copy is required, otherwise in-place modification.
 */
export function choice(array, weights = null, size = null) {
    if (!Array.isArray(array) || array.length === 0) {
        throw new Error("Items array can not be empty.")
    }
    
    if (weights !== null) {
        if (!Array.isArray(weights) || weights.length !== array.length) {
            throw new Error("array.length != weights.length.")
        }
        const totalWeight = weights.reduce((accumulatedWeight, weight) => accumulatedWeight + weight, 0);
        return baseGenerator(() => chooseOneWeighted(array, weights, totalWeight), size)
    }
    else {
        return baseGenerator(() => chooseOneUniform(array), size)
    }
}