export class ImportPipelineError extends Error {
    url;
    stageName;
    cause;
    constructor(message, options) {
        super(message);
        this.name = "ImportPipelineError";
        this.url = options.url;
        this.stageName = options.stageName;
        this.cause = options.cause;
    }
}
export class ImportPipeline {
    stages = [];
    addStage(stage) {
        if (this.stages.some((existing) => existing.name === stage.name)) {
            throw new Error(`Import stage already exists: ${stage.name}`);
        }
        this.stages.push(stage);
        return this;
    }
    async run(input, context) {
        const completed = [];
        let current = input;
        try {
            for (const stage of this.stages) {
                this.throwIfAborted(context, stage.name);
                if (stage.canRun && !(await this.withAbort(stage.canRun(current, context), context, stage.name))) {
                    this.report(context, stage.name, "skipped");
                    continue;
                }
                this.report(context, stage.name, "started");
                current = await this.withAbort(stage.run(current, context), context, stage.name);
                completed.push({ stage, output: current });
                this.throwIfAborted(context, stage.name);
                this.report(context, stage.name, "completed");
            }
            return current;
        }
        catch (error) {
            for (const entry of completed.reverse()) {
                if (!entry.stage.rollback) {
                    continue;
                }
                this.report(context, entry.stage.name, "rollback-started");
                try {
                    await this.withAbort(entry.stage.rollback(entry.output, context), context, entry.stage.name);
                    this.report(context, entry.stage.name, "rollback-completed");
                }
                catch (rollbackError) {
                    throw new ImportPipelineError(`Import pipeline rollback failed at stage ${entry.stage.name} for ${context.url}`, {
                        url: context.url,
                        stageName: entry.stage.name,
                        cause: rollbackError
                    });
                }
            }
            throw this.toPipelineError(error, context);
        }
    }
    async withAbort(operation, context, stageName) {
        if (!context.signal) {
            return operation;
        }
        this.throwIfAborted(context, stageName);
        return new Promise((resolve, reject) => {
            const abort = () => reject(this.createAbortError(context, stageName));
            context.signal?.addEventListener("abort", abort, { once: true });
            Promise.resolve(operation).then((value) => {
                context.signal?.removeEventListener("abort", abort);
                resolve(value);
            }, (error) => {
                context.signal?.removeEventListener("abort", abort);
                reject(error);
            });
        });
    }
    throwIfAborted(context, stageName) {
        if (context.signal?.aborted) {
            throw this.createAbortError(context, stageName);
        }
    }
    createAbortError(context, stageName) {
        return new ImportPipelineError(stageName ? `Import pipeline aborted at stage ${stageName} for ${context.url}` : `Import pipeline aborted for ${context.url}`, { url: context.url, stageName, cause: context.signal?.reason });
    }
    toPipelineError(error, context) {
        if (error instanceof ImportPipelineError) {
            return error;
        }
        if (error instanceof Error) {
            return new ImportPipelineError(`Import pipeline failed for ${context.url}: ${error.message}`, {
                url: context.url,
                cause: error
            });
        }
        return new ImportPipelineError(`Import pipeline failed for ${context.url}: ${String(error)}`, {
            url: context.url,
            cause: error
        });
    }
    report(context, stageName, status) {
        context.onProgress?.({ url: context.url, stageName, status });
    }
}
//# sourceMappingURL=ImportPipeline.js.map