"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OperationTree = void 0;
/**
 * Given a set of traits, construct a least-clicks optimized path with which to evaluate the operations from the passed Operator.
 * This reduces the number of clicks on the SFV platform which leads to much faster scraping and a lower likelyhood of bot detection.
 */
class OperationTree {
    /**
     * Constructs a new OperationTree with the given traits and operations. Filters out traits that are not supported.
     * @param trait_set The traits which this processor will collect.
     * @param operations The operations (from the operator) which will be used to collect the traits.
     */
    constructor(trait_set, operations) {
        this.operations = operations;
        /**
         * In-order mapping of traits.
         */
        this.dependencies = new Map();
        /**
         * Any warnings generated during construction
         */
        this.warnings = [];
        const working_set = new Set();
        const unsorted_traits = Array.from(trait_set.keys());
        const traits = unsorted_traits.sort();
        //Set up a valid working set by filtering out unsupported traits and dependencies
        this.setupWorkingSet(traits, this.operations, working_set);
        //Set up the dependencies tree
        this.setupDependenciesTree(working_set, this.operations);
    }
    setupWorkingSet(traits, operations, working_set) {
        for (const trait of traits) {
            // Filter out unsupported base traits.
            if (operations[trait] == undefined) {
                this.warnings.push(`The trait '${trait}' does not seem to be defined on the current operator. Check the spelling of the traits you have defined under "operations"`);
                continue;
            }
            //If the trait has no dependencies move it to the set of traits without dependencies
            if (operations[trait].depends_on == undefined) {
                this.dependencies.set(trait, undefined);
            }
            //otherwise, add it to the working set.
            else {
                const dependency = operations[trait].depends_on;
                // Filter out unsupported dependencies.
                if (operations[dependency] == undefined) {
                    this.warnings.push(`The trait '${trait}' has a dependency '${dependency}' which does not seem to be defined on the current operator. Check the spelling of the dependency you have defined under the operator '${trait}'`);
                    continue;
                }
                working_set.add(trait);
            }
        }
    }
    setupDependenciesTree(working_set, operations) {
        for (const trait of working_set) {
            //All entries here must have dependencies because of the way 'working_set' is populated, unless the underlying collection/entry is somehow modified in the interim.
            // if (dependency == undefined) {
            //     throw new Error(`Trait '${trait}' has an undefined dependency but is in the working set!`);
            // }
            try {
                //This sets up the dependencies tree which will then be used to build the in-order traversal map.
                this.recursiveDependency(trait, operations);
            }
            catch (error) {
                if (error instanceof RangeError) {
                    this.warnings.push(`Error building dependency tree started at trait '${trait}' due to circular dependency. Check operator which defines:\n${JSON.stringify(operations)} \n` +
                        `Context: dependency=${trait}`);
                }
            }
        }
    }
    /**
     * Gets the operations to be executed, in order.
     */
    getOrderedOperations() {
        const results = new Array();
        for (const dependency of this.dependencies.keys())
            //Only do recursion on root nodes
            if (this.operations[dependency].depends_on == undefined) {
                this.recursiveAdd(dependency, results);
            }
        return results;
    }
    /**
     * Gets the dependencies pair map.
     * @returns The map of dependencies.
     */
    getDependencyPairs() {
        return new Map(this.dependencies);
    }
    /**
     * Returns any warnings created during construction.
     * @returns Warnings encountered during construction.
     */
    getWarnings() {
        return this.warnings;
    }
    /**
     * Recursively adds operations, in order, to the results array. Does not check if the operation is supported by the current operator.
     * @param trait The trait to start the recursion from.
     * @param results_set The results set.
     */
    recursiveAdd(trait, results) {
        if (this.operations[trait] == undefined) {
            throw new Error(`Operation for ${trait} is undefined on current operator. Were operations added or removed since the last time the operations tree was computed?`);
        }
        const cleanup = this.operations[trait].cleanup;
        const run = this.operations[trait].run;
        if (run.length != 1) {
            if (cleanup && cleanup.length != 0) {
                this.warnings.push(`The function 'run' on trait '${trait}' has an incorrect number of arguments (expected: ${1} actual: ${run.length})\nThe function 'cleanup' on trait '${trait}' has an incorrect number of arguments (expected: ${0} actual: ${cleanup.length})`);
                return;
            }
            else {
                this.warnings.push(`The function 'run' on trait '${trait}' has an incorrect number of arguments (expected: ${1} actual: ${run.length})`);
                return;
            }
        }
        if (cleanup && cleanup.length != 0) {
            this.warnings.push(`The function 'cleanup' on trait '${trait}' has an incorrect number of arguments (expected: ${0} actual: ${cleanup.length})`);
            return;
        }
        results.push([trait, this.operations[trait].run]);
        const next = this.dependencies.get(trait);
        if (next) {
            this.recursiveAdd(next, results);
        }
        if (cleanup != undefined) {
            results.push([trait, () => cleanup()]);
        }
        else {
            //push an empty promise as cleanup (this is required to skip traits that have failed to gather so that their dependants are not evaluated)
            results.push([trait, async () => { }]);
        }
    }
    /**
     * Recursively finds dependencies for the given trait.
     * @param trait The trait to find dependencies for
     * @param operations Reference to the operations object
     */
    recursiveDependency(trait, operations) {
        const dependency = operations[trait].depends_on;
        if (dependency == undefined) {
            return;
        }
        this.dependencies.set(dependency, trait);
        this.recursiveDependency(dependency, operations);
    }
}
exports.OperationTree = OperationTree;
//# sourceMappingURL=operation_tree.js.map