"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JobTracker = exports.JobDetails = void 0;
const utils_1 = require("../utils");
class JobDetails {
    constructor(key, before, after, preconditions) {
        this.key = key;
        this.before = [...before];
        this.after = [...after];
        this.preconditions = preconditions;
    }
}
exports.JobDetails = JobDetails;
class JobTracker {
    constructor(jobs) {
        this.jobs = jobs;
        this.keyToDetails = new Map();
        this.dirtyKeys = new Set();
        this.pendingKeys = new Set();
        this.alreadyCompletedOnce = new Set();
        jobs.forEach((j) => {
            this.keyToDetails.set(j.key, j);
            this.dirtyKeys.add(j.key);
        });
    }
    /**
     * Marks the given `key` as dirty. If `recursive` is set
     * to `true` then all dependants will also be marked dirty, i.e. all
     * `JobDetails.after` keys will be marked as dirty.
     */
    markDirty(key, recursive = false) {
        if (!this.keyToDetails.has(key)) {
            throw Error(`unknown job key: ${key}`);
        }
        if (this.dirtyKeys.has(key)) {
            return;
        }
        this.dirtyKeys.add(key);
        if (recursive) {
            this.getDetails(key).after.forEach((after) => this.markDirty(after, true));
        }
    }
    /**
     * Marks the given `key` as currently being processed.
     */
    markProcessing(key) {
        if (!this.keyToDetails.has(key)) {
            throw Error(`unknown job key: ${key}`);
        }
        this.dirtyKeys.delete(key);
        this.pendingKeys.add(key);
    }
    /**
     * Marks the given `key` as processed and returns `true` iff the given `key`
     * has been processed the first time.
     *
     * `outputChanged` specifies whether the output generated by the job has changed and as such
     * all dependants should also be recompiled now, i.e. all `JobDetails.after` keys will be marked
     * as dirty. Please note that on the first processing time `outputChanged` will always be treated
     * as `true`.
     */
    markCompleted(key, outputChanged) {
        if (!this.keyToDetails.has(key)) {
            throw Error(`unknown job key: ${key}`);
        }
        this.pendingKeys.delete(key);
        utils_1.debug(`(JobTracker) [${key}] completed - output changed: ${outputChanged}`);
        let firstCompletion = false;
        if (!this.alreadyCompletedOnce.has(key)) {
            this.alreadyCompletedOnce.add(key);
            firstCompletion = true;
        }
        if (outputChanged || firstCompletion) {
            this.getDetails(key).after.forEach((after) => this.markDirty(after, true));
        }
        return firstCompletion;
    }
    /**
     * Get the next key ready for processing.
     *
     * The return value of this method will be as follows:
     * - a `string` if there is a key to be processed
     * - `null` if all keys have been processed completely
     * - `undefined` if there is not yet a key available due to pending processing operations
     */
    getNextKey() {
        if (this.dirtyKeys.size === 0 && this.pendingKeys.size === 0) {
            return null;
        }
        const ready = [...this.dirtyKeys.values()]
            .map((key) => this.getDetails(key))
            // Check if a dependency is still pending...
            .filter((details) => details.before.filter((beforeKey) => this.isDirtyOrPending(beforeKey)).length === 0)
            .filter((details) => {
            let preconditionsDone = true;
            details.preconditions.forEach((precondition) => (preconditionsDone =
                preconditionsDone && precondition(details.key)));
            return preconditionsDone;
        })
            // Check if the same plugin is already being compiled (can't run in parallel)
            .filter((details) => !this.pendingKeys.has(details.key))
            .map((details) => details.key);
        return ready.length > 0 ? ready[0] : undefined;
    }
    isJobDone(key) {
        return !this.dirtyKeys.has(key) && !this.pendingKeys.has(key);
    }
    getDetails(key) {
        const details = this.keyToDetails.get(key);
        if (!details) {
            throw Error(`unexpectly missing key ${key}`);
        }
        return details;
    }
    isDirtyOrPending(key) {
        return this.dirtyKeys.has(key) || this.pendingKeys.has(key);
    }
}
exports.JobTracker = JobTracker;
//# sourceMappingURL=JobTracker.js.map