import { assert } from "../../../core/assert.js";
import { array_push_if_unique } from "../../../core/collection/array/array_push_if_unique.js";
import { buildPromiseChain } from "../../../core/process/buildPromiseChain.js";
import { ResourceAllocationSolver } from "./ResourceAllocationSolver.js";

/**
 * @template A
 * @example
 *     const allocator = new StrategicResourceAllocator();
 *
 *     allocator.addTacticalModule(module); // add some tactical modules
 *
 *     const resources = [new Resource(1, "a")];
 *     const actions = await allocator.allocate(resources); // list of actions that use resources
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class StrategicResourceAllocator {

    /**
     *
     * @type {TacticalModule[]}
     */
    modules = [];

    /**
     * @private
     * @type {Array<function(ActionSequence[]):Promise<ActionSequence[]>>}
     */
    sequenceTransformers = [];


    /**
     * Add a sequence transformer, these are applied as a final step during resource allocation
     * @param {function(ActionSequence[]):Promise<ActionSequence[]>} t
     */
    addTransformer(t) {
        assert.isFunction(t, 't');

        this.sequenceTransformers.push(t);
    }

    /**
     *
     * @param {TacticalModule} module
     * @returns {boolean}
     */
    addTacticalModule(module) {
        assert.defined(module, 'module');
        assert.notNull(module, 'module');
        assert.equal(module.isTacticalModule, true, 'module.TacticalModule !== true');

        return array_push_if_unique(this.modules, module);
    }

    /**
     * @template A
     * @param {Resource[]} resources
     * @returns {Promise<A[]>} actions
     */
    allocate(resources) {
        assert.isArray(resources, 'resources');


        /**
         *
         * @type {Map<ResourceAllocationBid, TacticalModule>}
         */
        const bids = new Map();


        return new Promise((resolve, reject) => {
            const moduleResults = this.modules.map(m => {
                const promise = m.collectBids(resources);

                assert.defined(promise, 'promise');
                assert.notNull(promise, 'promise');
                assert.isFunction(promise.then, "promise.then");

                promise.then(moduleBids => {

                    assert.isArray(moduleBids, 'moduleBids');

                    moduleBids.forEach(b => bids.set(b, m));

                });

                return promise;
            });

            resolve(moduleResults);
        })
            .then(moduleResults => Promise.all(moduleResults))
            .then(() => {
                /**
                 *
                 * @type {ResourceAllocationSolver}
                 */
                const solver = new ResourceAllocationSolver();

                //set resources
                solver.addResources(resources);
                //set bids
                bids.forEach((m, b) => solver.addBid(b));

                /**
                 *
                 * @type {ResourceAllocationBid[]}
                 */
                const allocations = solver.solve();

                const actionSequences = allocations.map(a => a.actions);

                //sort action sequences based on their priorities, higher priority sequences first
                actionSequences.sort((a, b) => {

                    const aPriority = a.getPriority();
                    const bPriority = b.getPriority();

                    return bPriority - aPriority
                });

                return actionSequences;
            })
            .then(sequences => {
                // Apply sequence transformers
                const sequenceTransformers = this.sequenceTransformers;
                const nSequenceTransformers = sequenceTransformers.length;

                const factories = [];

                for (let i = 0; i < nSequenceTransformers; i++) {
                    const sequenceProcessor = sequenceTransformers[i];

                    factories.push(input => sequenceProcessor(input));
                }

                return buildPromiseChain({ factories, head: Promise.resolve(sequences) });
            })
            .then(sequences => {
                //extract actions from sequences in order
                const actions = sequences.map(s => s.actions).flat();

                return actions;
            });
    }
}
