import { assert } from "../../../../core/assert.js";
import { weightedRandomFromArray } from "../../../../core/collection/array/weightedRandomFromArray.js";
import { seededRandom } from "../../../../core/math/random/seededRandom.js";
import { Behavior } from "../Behavior.js";
import { WeightedElement } from "./WeightedElement.js";

/**
 * Picks a random behavior to execute.
 * The selection is done during initialization, and that selected behavior will then run to completion.
 * NOTE: Weights are relative, so they don't need to add up to any specific value.
 *
 * @example
 *  const behavior = WeightedRandomBehavior.from([
 *      WeightedElement.from(A, 1), // A is some behavior
 *      WeightedElement.from(B, 70), // B is some behavior
 *  ]); // B will be 70 times more likely to be picked than A
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class WeightedRandomBehavior extends Behavior {

    /**
     *
     * @type {WeightedElement<Behavior>[]} elements
     * @private
     */
    __elements = [];

    /**
     *
     * @type {function(): number}
     * @private
     */
    __random = seededRandom(91512);

    /**
     *
     * @type {Behavior|undefined}
     * @private
     */
    __selected = undefined;

    /**
     *
     * @param {number} v
     */
    setRandomSeed(v) {
        this.__random.setCurrentSeed(v);
    }

    /**
     *
     * @param {WeightedElement<Behavior>[]} elements Must not be empty
     */
    setElements(elements) {
        assert.defined(elements, 'elements');
        assert.isArray(elements, 'elements');
        assert.greaterThan(elements.length, 0, 'elements.length === 0');

        this.__elements = [];

        const count = elements.length;
        for (let i = 0; i < count; i++) {
            const element = elements[i];
            this.addElement(element);
        }
    }

    /**
     *
     * @param {WeightedElement<Behavior>} e
     */
    addElement(e) {

        assert.defined(e, 'element');
        assert.notNull(e, 'element');

        assert.isObject(e, 'element');

        assert.equal(e.isWeightedElement, true, 'element.isWeightedElement !== true');
        assert.equal(e.data.isBehavior, true, 'element.data.isWeightedElement !== true');

        this.__elements.push(e);
    }

    /**
     *
     * @param {WeightedElement<Behavior>[]} elements
     * @returns {WeightedRandomBehavior}
     */
    static from(elements) {
        assert.defined(elements, 'elements');

        const r = new WeightedRandomBehavior();

        r.setElements(elements);

        return r;
    }

    __select() {
        /**
         *
         * @type {WeightedElement<Behavior>|undefined}
         */
        const weightedElement = weightedRandomFromArray(this.__elements, this.__random, WeightedElement.getWeight);

        const selected = weightedElement.data;

        assert.equal(selected.isBehavior, true, 'selected.isBehavior !== true');

        this.__selected = selected;
    }

    initialize(context) {
        super.initialize(context);

        this.__select();

        this.__selected.initialize(context);
    }

    finalize() {
        super.finalize();

        this.__selected.finalize();

        this.__selected = undefined;
    }

    tick(timeDelta) {
        return this.__selected.tick(timeDelta);
    }
}
