import {
  clampLimit,
  decodeKeysetCursor,
  encodeKeysetCursor,
  toJobRunSummary
} from "./chunk-L3LZWWSX.js";
import {
  jobRuns
} from "./chunk-OKXZ63IA.js";
import {
  JOBS_MULTI_TENANT,
  JOB_ORCHESTRATOR
} from "./chunk-ZPL74UQN.js";
import {
  MissingTenantIdError
} from "./chunk-T4BIIU5E.js";
import {
  DRIZZLE
} from "./chunk-U64T4YZE.js";
import {
  __decorateClass,
  __decorateParam
} from "./chunk-2E224ZSN.js";

// runtime/subsystems/jobs/job-run-service.drizzle-backend.ts
import { Inject, Injectable } from "@nestjs/common";
import { and, asc, desc, eq, gte, inArray, isNull, lt, or, sql } from "drizzle-orm";
var NON_TERMINAL_STATUSES = [
  "pending",
  "running",
  "waiting"
];
var DrizzleJobRunService = class {
  constructor(db, orchestrator, multiTenant) {
    this.db = db;
    this.orchestrator = orchestrator;
    this.multiTenant = multiTenant;
  }
  db;
  orchestrator;
  multiTenant;
  /**
   * JOB-8 — produce the tenant WHERE fragment (or `null` to opt out).
   * Returns `null` when multi-tenancy is off (caller skips the predicate).
   * Throws `MissingTenantIdError` when on + `undefined`.
   * When on + explicit `null`, filters `tenant_id IS NULL`.
   */
  tenantCondition(method, tenantId) {
    if (!this.multiTenant) return null;
    if (tenantId === void 0) throw new MissingTenantIdError(method);
    return tenantId === null ? isNull(jobRuns.tenantId) : eq(jobRuns.tenantId, tenantId);
  }
  async listForScope(entityType, entityId, opts = {}) {
    const conditions = [
      eq(jobRuns.scopeEntityType, entityType),
      eq(jobRuns.scopeEntityId, entityId)
    ];
    const tenantCond = this.tenantCondition("listForScope", opts.tenantId);
    if (tenantCond) conditions.push(tenantCond);
    if (opts.status) {
      if (Array.isArray(opts.status)) {
        conditions.push(inArray(jobRuns.status, opts.status));
      } else {
        conditions.push(eq(jobRuns.status, opts.status));
      }
    }
    if (opts.jobType) {
      conditions.push(eq(jobRuns.jobType, opts.jobType));
    }
    const orderCol = (() => {
      switch (opts.orderBy) {
        case "created_at asc":
          return asc(jobRuns.createdAt);
        case "run_at desc":
          return desc(jobRuns.runAt);
        case "run_at asc":
          return asc(jobRuns.runAt);
        case "created_at desc":
        default:
          return desc(jobRuns.createdAt);
      }
    })();
    let q = this.db.select().from(jobRuns).where(and(...conditions)).orderBy(orderCol).$dynamic();
    if (typeof opts.limit === "number") {
      q = q.limit(opts.limit);
    }
    if (typeof opts.offset === "number") {
      q = q.offset(opts.offset);
    }
    const rows = await q;
    return rows;
  }
  async cancelForScope(entityType, entityId, opts = {}) {
    const tenantCond = this.tenantCondition("cancelForScope", opts.tenantId);
    const conditions = [
      eq(jobRuns.scopeEntityType, entityType),
      eq(jobRuns.scopeEntityId, entityId),
      inArray(jobRuns.status, NON_TERMINAL_STATUSES)
    ];
    if (tenantCond) conditions.push(tenantCond);
    const rows = await this.db.select({ id: jobRuns.id }).from(jobRuns).where(and(...conditions));
    for (const { id } of rows) {
      await this.orchestrator.cancel(id, {
        cascade: true,
        tenantId: opts.tenantId
      });
    }
  }
  async rescheduleForScope(entityType, entityId, newRunAt, opts = {}) {
    const tenantCond = this.tenantCondition("rescheduleForScope", opts.tenantId);
    const conditions = [
      eq(jobRuns.scopeEntityType, entityType),
      eq(jobRuns.scopeEntityId, entityId),
      eq(jobRuns.status, "pending")
    ];
    if (tenantCond) conditions.push(tenantCond);
    await this.db.update(jobRuns).set({ runAt: newRunAt, updatedAt: /* @__PURE__ */ new Date() }).where(and(...conditions));
  }
  async countByPoolAndStatus(tenantId) {
    const tenantCond = this.tenantCondition("countByPoolAndStatus", tenantId);
    const rows = await this.db.select({
      pool: jobRuns.pool,
      status: jobRuns.status,
      count: sql`count(*)::int`.as("count")
    }).from(jobRuns).where(tenantCond ?? void 0).groupBy(jobRuns.pool, jobRuns.status);
    return rows.map((r) => ({
      pool: r.pool,
      status: r.status,
      count: Number(r.count)
    }));
  }
  async listRecentFailed(limit, tenantId) {
    const conditions = [eq(jobRuns.status, "failed")];
    const tenantCond = this.tenantCondition("listRecentFailed", tenantId);
    if (tenantCond) conditions.push(tenantCond);
    const rows = await this.db.select().from(jobRuns).where(and(...conditions)).orderBy(desc(jobRuns.finishedAt), desc(jobRuns.updatedAt)).limit(limit);
    return rows.map((r) => ({
      runId: r.id,
      jobType: r.jobType,
      pool: r.pool,
      scopeEntityType: r.scopeEntityType,
      scopeEntityId: r.scopeEntityId,
      tenantId: r.tenantId,
      attempts: r.attempts,
      errorMessage: r.error?.message ?? null,
      failedAt: r.finishedAt ?? r.updatedAt,
      createdAt: r.createdAt
    }));
  }
  async listJobRuns(query = {}) {
    const limit = clampLimit(query.limit);
    const conditions = [];
    const tenantCond = this.tenantCondition("listJobRuns", query.tenantId);
    if (tenantCond) conditions.push(tenantCond);
    if (query.poolId) conditions.push(eq(jobRuns.pool, query.poolId));
    if (query.rootRunId) conditions.push(eq(jobRuns.rootRunId, query.rootRunId));
    if (query.status) conditions.push(eq(jobRuns.status, query.status));
    if (query.since) conditions.push(gte(jobRuns.createdAt, query.since));
    if (query.cursor) {
      const keyset = decodeKeysetCursor(query.cursor);
      if (keyset) {
        conditions.push(
          or(
            lt(jobRuns.createdAt, keyset.createdAt),
            and(
              eq(jobRuns.createdAt, keyset.createdAt),
              lt(jobRuns.id, keyset.id)
            )
          )
        );
      }
    }
    const rows = await this.db.select().from(jobRuns).where(conditions.length > 0 ? and(...conditions) : void 0).orderBy(desc(jobRuns.createdAt), desc(jobRuns.id)).limit(limit + 1);
    const hasMore = rows.length > limit;
    const page = hasMore ? rows.slice(0, limit) : rows;
    const items = page.map(toJobRunSummary);
    const last = page[page.length - 1];
    const nextCursor = hasMore && last ? encodeKeysetCursor({ createdAt: last.createdAt, id: last.id }) : null;
    return { items, nextCursor };
  }
  /**
   * Internal helper used by cascade paths (not on the public protocol).
   * Exposed as a public method on the concrete class so infrastructure
   * code (cascade tests, debug tools) can call it without a cast.
   */
  async findByRootRunId(rootRunId) {
    const rows = await this.db.select().from(jobRuns).where(eq(jobRuns.rootRunId, rootRunId));
    return rows;
  }
};
DrizzleJobRunService = __decorateClass([
  Injectable(),
  __decorateParam(0, Inject(DRIZZLE)),
  __decorateParam(1, Inject(JOB_ORCHESTRATOR)),
  __decorateParam(2, Inject(JOBS_MULTI_TENANT))
], DrizzleJobRunService);

export {
  DrizzleJobRunService
};
//# sourceMappingURL=chunk-5LXOJGO2.js.map