import { KeetaAnchorQueueRunner } from './index.js';
import { MethodLogger } from './internal.js';
/**
 * A KeetaAnchorQueueRunner that uses `any` for the user types
 * This is helpful for expressing pipelines where the inputs
 * and outputs of various stages are not known ahead of time
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
class KeetaAnchorQueueRunnerAny extends KeetaAnchorQueueRunner {
    processor() { throw (new Error('not implemented')); }
    decodeRequest() { throw (new Error('not implemented')); }
    decodeResponse() { throw (new Error('not implemented')); }
    encodeRequest() { throw (new Error('not implemented')); }
    encodeResponse() { throw (new Error('not implemented')); }
}
const symbolFirst = Symbol('first');
const symbolLast = Symbol('last');
/**
 * Abstract base class for queue advanced pipelines -- this provides
 * a standardized way to implement custom processing pipelines that
 * consist of multiple stages with custom behavior
 */
export class KeetaAnchorQueuePipelineAdvanced {
    id;
    baseQueue;
    logger;
    queues;
    initPromise;
    destroyed = false;
    static StageID = {
        first: symbolFirst,
        last: symbolLast
    };
    constructor(options) {
        this.logger = options.logger;
        this.id = options.id ?? crypto.randomUUID();
        this.baseQueue = options.baseQueue;
        this.queues = [];
    }
    methodLogger(method) {
        return (MethodLogger(this.logger, {
            class: 'KeetaAnchorQueuePipelineAdvanced',
            file: 'src/lib/queue/pipeline.ts',
            method: method,
            instanceID: this.id
        }));
    }
    /**
     * Create a new queue to use in a pipeline stage
     * It will be released when the pipeline is destroyed
     *
     * @param name The name of the queue
     * @returns The created queue
     */
    async createQueue(name) {
        const retval = await this.baseQueue.partition(name);
        this.queues.push(retval);
        return (retval);
    }
    async init() {
        const logger = this.methodLogger('init');
        if (this.destroyed) {
            throw (new Error('Pipeline has been destroyed'));
        }
        if (this.initPromise !== undefined) {
            return (await this.initPromise);
        }
        this.initPromise = (async () => {
            try {
                await this.createPipeline();
            }
            catch (error) {
                logger?.error('Error initializing pipeline:', error);
                try {
                    await this.destroy();
                }
                catch {
                    /* Ignore */
                }
                throw (error);
            }
        })();
        return (await this.initPromise);
    }
    async add(request) {
        await this.init();
        const stage1 = this.getStage(KeetaAnchorQueuePipelineAdvanced.StageID.first);
        return (await stage1.add(request));
    }
    async get(id) {
        await this.init();
        const firstStage = this.getStage(KeetaAnchorQueuePipelineAdvanced.StageID.first);
        const finalStage = this.getStage(KeetaAnchorQueuePipelineAdvanced.StageID.last);
        const firstEntry = await firstStage.get(id);
        const finalOutput = await finalStage.get(id);
        if (finalOutput === null) {
            return (null);
        }
        return ({
            ...finalOutput,
            request: firstEntry?.request ?? null
        });
    }
    async run(options) {
        await this.init();
        const logger = this.methodLogger('run');
        const stage1 = this.getStage(KeetaAnchorQueuePipelineAdvanced.StageID.first);
        let retval = true;
        try {
            retval = await stage1.run(options);
        }
        catch (error) {
            logger?.error('Error running stage processor:', error);
        }
        return (retval);
    }
    async maintain() {
        await this.init();
        const logger = this.methodLogger('maintain');
        const stage1 = this.getStage(KeetaAnchorQueuePipelineAdvanced.StageID.first);
        try {
            await stage1.maintain();
        }
        catch (error) {
            logger?.error('Error running stage maintenance:', error);
        }
    }
    async destroy() {
        const logger = this.methodLogger('destroy');
        if (this.destroyed) {
            return;
        }
        this.destroyed = true;
        await this.init();
        for (let index = 0; index < this.queues.length; index++) {
            const queue = this.queues[index];
            if (queue === undefined) {
                continue;
            }
            try {
                logger?.debug(`Destroying queue for stage "#${index}"`);
                await queue.destroy();
            }
            catch (error) {
                logger?.error(`Error destroying queue for stage "#${index}":`, error);
            }
        }
    }
    async [Symbol.asyncDispose]() {
        const logger = this.methodLogger('asyncDispose');
        try {
            await this.destroy();
        }
        catch (error) {
            logger?.error('Error during async dispose:', error);
        }
    }
}
/**
 * Abstract base class for queue basic pipelines -- this provides
 * a standardized way to implement custom processing pipelines that
 * consist of multiple stages but do not require batching
 */
export class KeetaAnchorQueuePipelineBasic extends KeetaAnchorQueuePipelineAdvanced {
    stageRunners;
    constructor(options) {
        super(options);
        /*
         * We start out with no stages, but the createPipeline method
         * will be run during initialization to set them up
         */
        // @ts-ignore
        this.stageRunners = [];
    }
    methodLogger(method) {
        return (MethodLogger(this.logger, {
            class: 'KeetaAnchorQueuePipelineBasic',
            file: 'src/lib/queue/pipeline.ts',
            method: method,
            instanceID: this.id
        }));
    }
    getStage(stageID) {
        if (this.initPromise === undefined) {
            throw (new Error('Pipeline not initialized'));
        }
        if (stageID === KeetaAnchorQueuePipelineAdvanced.StageID.first) {
            const runner = this.stageRunners[0];
            if (runner !== undefined) {
                // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
                return runner;
            }
            throw (new Error('First stage runner not found'));
        }
        else if (stageID === KeetaAnchorQueuePipelineAdvanced.StageID.last) {
            for (let index = this.stageRunners.length - 1; index >= 0; index--) {
                const runner = this.stageRunners[index];
                if (runner !== undefined) {
                    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
                    return runner;
                }
            }
            throw (new Error('Last stage runner not found'));
        }
        else {
            for (let index = 0; index < this.stages.length; index++) {
                const stage = this.stages[index];
                if (stage?.name === stageID) {
                    const runner = this.stageRunners[index];
                    if (runner !== undefined) {
                        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
                        return runner;
                    }
                }
            }
            return (null);
        }
    }
    /**
     * Create the pipeline from the user-defined stages
     */
    async createPipeline() {
        const logger = this.methodLogger('createPipeline');
        let lastRunner = undefined;
        for (const stage of this.stages) {
            if (stage === undefined) {
                break;
            }
            try {
                logger?.debug(`Initializing queue for stage "${stage.name}"`);
                const queue = await this.createQueue(stage.name);
                logger?.debug(`Initializing stage processor for stage "${stage.name}"`);
                /**
                 * We have to use this type because we cannot express the relationship
                 * between the various generic types in the stages array and the stageRunners array
                 */
                // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
                const runnerClass = stage.runner;
                const runnerArgsFirst = stage.args?.[0];
                const runnerArgs0 = {
                    id: `${this.id}::runner::${stage.name}`,
                    queue: queue,
                    logger: this.logger,
                    ...runnerArgsFirst
                };
                const runnerArgs = [runnerArgs0, ...(stage.args?.slice(1) ?? [])];
                /*
                 * We check the first parameter's type above, but all the remaining
                 * parameters are user-defined and we cannot validate them here
                 * so we cast to the tuple `any` type to avoid type checking
                 */
                // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-argument
                const runner = new runnerClass(...runnerArgs);
                /*
                 * The type assertions here are necessary because we cannot
                 * express the relationship between the various generic
                 * types in the stages array and the stageRunners array
                 *
                 * The ts-ignore is necessary because TypeScript does not allow us to push to a readonly array.
                 */
                // @ts-ignore
                // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
                this.stageRunners.push(runner);
                lastRunner?.pipe(runner);
                lastRunner = runner;
            }
            catch (error) {
                logger?.error(`Error initializing stage processor for stage "${stage.name}":`, error);
                this.destroy().catch(function () { });
            }
        }
    }
    async destroy() {
        const logger = this.methodLogger('destroy');
        if (this.destroyed) {
            return;
        }
        this.destroyed = true;
        for (let index = 0; index < this.stageRunners.length; index++) {
            const runner = this.stageRunners[index];
            const stage = this.stages[index] ?? { name: '' };
            if (runner === undefined) {
                continue;
            }
            try {
                logger?.debug(`Destroying stage processor for stage "${stage.name}"`);
                await runner.destroy();
            }
            catch (error) {
                logger?.error(`Error destroying stage processor for stage "${stage.name}":`, error);
            }
        }
        await super.destroy();
    }
}
//# sourceMappingURL=pipeline.js.map