import {
  JOB_HANDLER_REGISTRY
} from "../../../chunk-7P5ODGLA.js";
import {
  jobRuns
} from "../../../chunk-OKXZ63IA.js";
import "../../../chunk-GYGNEQSC.js";
import "../../../chunk-2E224ZSN.js";

// runtime/subsystems/jobs/job-worker.bullmq-backend.ts
import { Logger } from "@nestjs/common";
import { eq } from "drizzle-orm";
function serialiseError(err, attempt, retryable) {
  const e = err;
  return {
    message: e?.message ?? String(err),
    stack: e?.stack,
    retryable,
    attempt
  };
}
var BullMQJobWorker = class _BullMQJobWorker {
  constructor(db, orchestrator, stepService, options, moduleRef) {
    this.db = db;
    this.orchestrator = orchestrator;
    this.stepService = stepService;
    this.options = options;
    this.moduleRef = moduleRef;
  }
  db;
  orchestrator;
  stepService;
  options;
  moduleRef;
  logger = new Logger(_BullMQJobWorker.name);
  worker = null;
  async onModuleInit() {
    let WorkerCtor;
    try {
      const mod = await import("bullmq");
      WorkerCtor = mod.Worker;
    } catch {
      throw new Error(
        'BullMQ backend requires the "bullmq" package. Install it with: npm install bullmq'
      );
    }
    this.worker = new WorkerCtor(
      this.options.queueName,
      // #6 / noImplicitAny — explicit annotations so the file stays
      // strict-clean even when consumers compile under a stricter tsconfig
      // than the one used to type-check the runtime tree.
      (job) => this.process(job),
      {
        connection: this.options.connection,
        concurrency: this.options.concurrency
      }
    );
    this.worker.on("failed", (job, err) => {
      if (!job) return;
      const attemptsMade = job.attemptsMade;
      const maxAttempts = job.opts.attempts ?? 1;
      if (attemptsMade >= maxAttempts) {
        void this.markFailed(job.data.runId, err, attemptsMade);
      }
    });
    this.logger.log(
      `BullMQ worker started: pool='${this.options.pool}' queue='${this.options.queueName}' concurrency=${this.options.concurrency}`
    );
  }
  async onModuleDestroy() {
    if (this.worker) {
      await this.worker.close();
      this.worker = null;
    }
  }
  /**
   * Process one BullMQ job. Returns the handler output (stored by BullMQ as
   * the job return value AND written to `job_run.output`). Throws on handler
   * failure so BullMQ applies the retry policy.
   */
  async process(job) {
    const { runId } = job.data;
    const [row] = await this.db.select().from(jobRuns).where(eq(jobRuns.id, runId)).limit(1);
    if (!row) {
      this.logger.warn(`process: job_run ${runId} not found; skipping`);
      return {};
    }
    const run = row;
    if (run.status === "canceled") {
      return {};
    }
    const registryEntry = JOB_HANDLER_REGISTRY.get(run.jobType);
    if (!registryEntry) {
      throw new Error(
        `No handler registered for jobType='${run.jobType}' (run ${run.id})`
      );
    }
    await this.db.update(jobRuns).set({
      status: "running",
      claimedAt: /* @__PURE__ */ new Date(),
      startedAt: /* @__PURE__ */ new Date(),
      attempts: job.attemptsMade + 1,
      updatedAt: /* @__PURE__ */ new Date()
    }).where(eq(jobRuns.id, run.id));
    const HandlerClass = registryEntry.handlerClass;
    const handler = this.moduleRef.get(
      HandlerClass,
      { strict: false }
    );
    const ctx = {
      input: run.input,
      run,
      step: this.makeStepFn(run),
      spawnChild: this.makeSpawnFn(run),
      logger: new Logger(`JobRun:${run.id}`)
    };
    const output = await handler.run(ctx);
    await this.db.update(jobRuns).set({
      status: "completed",
      output: output ?? {},
      finishedAt: /* @__PURE__ */ new Date(),
      updatedAt: /* @__PURE__ */ new Date()
    }).where(eq(jobRuns.id, run.id));
    return output ?? {};
  }
  async markFailed(runId, err, finalAttempts) {
    const [row] = await this.db.select().from(jobRuns).where(eq(jobRuns.id, runId)).limit(1);
    if (!row) return;
    const run = row;
    await this.db.update(jobRuns).set({
      status: "failed",
      attempts: finalAttempts,
      finishedAt: /* @__PURE__ */ new Date(),
      error: serialiseError(err, finalAttempts, false),
      updatedAt: /* @__PURE__ */ new Date()
    }).where(eq(jobRuns.id, runId));
    if (run.parentClosePolicy === "terminate") {
      try {
        await this.orchestrator.cancel(run.id, {
          cascade: true,
          reason: "parent-failed",
          tenantId: run.tenantId
        });
      } catch (cascadeErr) {
        this.logger.warn(
          `cascade on failed run ${run.id}: ${cascadeErr.message}`
        );
      }
    }
  }
  // ── ctx.step / ctx.spawnChild (mirror JobWorker) ──────────────────────────
  makeStepFn(run) {
    return async (stepId, fn, _opts) => {
      void _opts;
      const existing = await this.stepService.findStep(run.id, stepId);
      if (existing?.status === "completed") {
        return existing.output;
      }
      const nextAttempts = (existing?.attempts ?? 0) + 1;
      const seq = nextAttempts;
      await this.stepService.recordStep({
        jobRunId: run.id,
        stepId,
        kind: "task",
        seq,
        status: "running",
        startedAt: /* @__PURE__ */ new Date(),
        attempts: nextAttempts
      });
      try {
        const output = await fn();
        await this.stepService.recordStep({
          jobRunId: run.id,
          stepId,
          kind: "task",
          seq,
          status: "completed",
          output,
          finishedAt: /* @__PURE__ */ new Date(),
          attempts: nextAttempts
        });
        return output;
      } catch (err) {
        await this.stepService.recordStep({
          jobRunId: run.id,
          stepId,
          kind: "task",
          seq,
          status: "failed",
          error: serialiseError(err, nextAttempts, false),
          finishedAt: /* @__PURE__ */ new Date(),
          attempts: nextAttempts
        });
        throw err;
      }
    };
  }
  makeSpawnFn(run) {
    return async (type, input, opts) => {
      return this.orchestrator.start(type, input, {
        parentRunId: run.id,
        parentClosePolicy: opts?.closePolicy,
        runAt: opts?.runAt,
        priority: opts?.priority,
        tags: opts?.tags,
        triggerSource: "parent",
        triggerRef: run.id,
        tenantId: run.tenantId
      });
    };
  }
};
export {
  BullMQJobWorker
};
//# sourceMappingURL=job-worker.bullmq-backend.js.map