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

// runtime/subsystems/jobs/job-run-service.memory-backend.ts
import { Inject, Injectable } from "@nestjs/common";
var NON_TERMINAL_STATUSES = [
  "pending",
  "running",
  "waiting"
];
var MemoryJobRunService = class {
  constructor(store, orchestrator, multiTenant) {
    this.store = store;
    this.orchestrator = orchestrator;
    this.multiTenant = multiTenant;
  }
  store;
  orchestrator;
  multiTenant;
  /**
   * JOB-8 — produce a per-row predicate for the tenant gate.
   * Returns `null` when multi-tenancy is off (caller doesn't check).
   * Throws when on + `undefined`; matches `tenant_id IS NULL` on explicit
   * `null` to support cross-tenant background work.
   */
  tenantPredicate(method, tenantId) {
    if (!this.multiTenant) return null;
    if (tenantId === void 0) throw new MissingTenantIdError(method);
    return (r) => r.tenantId === tenantId;
  }
  async listForScope(entityType, entityId, opts = {}) {
    const statusFilter = opts.status ? Array.isArray(opts.status) ? new Set(opts.status) : /* @__PURE__ */ new Set([opts.status]) : null;
    const tenantCheck = this.tenantPredicate("listForScope", opts.tenantId);
    const rows = [];
    for (const r of this.store.runs.values()) {
      if (r.scopeEntityType !== entityType) continue;
      if (r.scopeEntityId !== entityId) continue;
      if (statusFilter && !statusFilter.has(r.status)) continue;
      if (opts.jobType && r.jobType !== opts.jobType) continue;
      if (tenantCheck && !tenantCheck(r)) continue;
      rows.push(r);
    }
    const orderBy = opts.orderBy ?? "created_at desc";
    rows.sort((a, b) => compareBy(a, b, orderBy));
    const offset = opts.offset ?? 0;
    const limit = opts.limit;
    const sliced = typeof limit === "number" ? rows.slice(offset, offset + limit) : rows.slice(offset);
    return sliced;
  }
  async cancelForScope(entityType, entityId, opts = {}) {
    const tenantCheck = this.tenantPredicate("cancelForScope", opts.tenantId);
    const ids = [];
    for (const r of this.store.runs.values()) {
      if (r.scopeEntityType !== entityType) continue;
      if (r.scopeEntityId !== entityId) continue;
      if (!NON_TERMINAL_STATUSES.includes(r.status)) continue;
      if (tenantCheck && !tenantCheck(r)) continue;
      ids.push(r.id);
    }
    for (const id of ids) {
      await this.orchestrator.cancel(id, {
        cascade: true,
        tenantId: opts.tenantId
      });
    }
  }
  async rescheduleForScope(entityType, entityId, newRunAt, opts = {}) {
    const tenantCheck = this.tenantPredicate("rescheduleForScope", opts.tenantId);
    for (const r of this.store.runs.values()) {
      if (r.scopeEntityType !== entityType) continue;
      if (r.scopeEntityId !== entityId) continue;
      if (r.status !== "pending") continue;
      if (tenantCheck && !tenantCheck(r)) continue;
      this.store.runs.set(r.id, {
        ...r,
        runAt: newRunAt,
        updatedAt: /* @__PURE__ */ new Date()
      });
    }
  }
  async countByPoolAndStatus(tenantId) {
    const tenantCheck = this.tenantPredicate("countByPoolAndStatus", tenantId);
    const map = /* @__PURE__ */ new Map();
    for (const r of this.store.runs.values()) {
      if (tenantCheck && !tenantCheck(r)) continue;
      const key = `${r.pool}\0${r.status}`;
      const cur = map.get(key);
      if (cur) {
        cur.count += 1;
      } else {
        map.set(key, { pool: r.pool, status: r.status, count: 1 });
      }
    }
    return Array.from(map.values());
  }
  async listRecentFailed(limit, tenantId) {
    const tenantCheck = this.tenantPredicate("listRecentFailed", tenantId);
    const failed = [];
    for (const r of this.store.runs.values()) {
      if (r.status !== "failed") continue;
      if (tenantCheck && !tenantCheck(r)) continue;
      failed.push(r);
    }
    failed.sort((a, b) => {
      const at = (a.finishedAt ?? a.updatedAt).getTime();
      const bt = (b.finishedAt ?? b.updatedAt).getTime();
      return bt - at;
    });
    return failed.slice(0, limit).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 tenantCheck = this.tenantPredicate("listJobRuns", query.tenantId);
    const keyset = query.cursor ? decodeKeysetCursor(query.cursor) : null;
    const matched = [];
    for (const r of this.store.runs.values()) {
      if (tenantCheck && !tenantCheck(r)) continue;
      if (query.poolId && r.pool !== query.poolId) continue;
      if (query.rootRunId && r.rootRunId !== query.rootRunId) continue;
      if (query.status && r.status !== query.status) continue;
      if (query.since && r.createdAt.getTime() < query.since.getTime()) continue;
      matched.push(r);
    }
    matched.sort((a, b) => {
      const dt = b.createdAt.getTime() - a.createdAt.getTime();
      if (dt !== 0) return dt;
      return a.id < b.id ? 1 : a.id > b.id ? -1 : 0;
    });
    const seeked = keyset ? matched.filter((r) => {
      const ct = r.createdAt.getTime();
      const kt = keyset.createdAt.getTime();
      if (ct < kt) return true;
      if (ct > kt) return false;
      return r.id < keyset.id;
    }) : matched;
    const hasMore = seeked.length > limit;
    const page = hasMore ? seeked.slice(0, limit) : seeked;
    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 };
  }
  /**
   * Direct lookup. Not on the protocol — concrete-class convenience for
   * tests. Matches `DrizzleJobRunService.findByRootRunId` in spirit; both
   * are debug / test helpers that sidestep the orchestrator.
   */
  findById(runId) {
    return this.store.runs.get(runId) ?? null;
  }
  /** Public counterpart to the Drizzle backend's `findByRootRunId` helper. */
  findByRootRunId(rootRunId) {
    const out = [];
    for (const r of this.store.runs.values()) {
      if (r.rootRunId === rootRunId) out.push(r);
    }
    return out;
  }
};
MemoryJobRunService = __decorateClass([
  Injectable(),
  __decorateParam(0, Inject(MemoryJobStore)),
  __decorateParam(1, Inject(JOB_ORCHESTRATOR)),
  __decorateParam(2, Inject(JOBS_MULTI_TENANT))
], MemoryJobRunService);
function compareBy(a, b, order) {
  switch (order) {
    case "created_at asc":
      return a.createdAt.getTime() - b.createdAt.getTime();
    case "run_at desc":
      return b.runAt.getTime() - a.runAt.getTime();
    case "run_at asc":
      return a.runAt.getTime() - b.runAt.getTime();
    case "created_at desc":
    default:
      return b.createdAt.getTime() - a.createdAt.getTime();
  }
}

export {
  MemoryJobRunService
};
//# sourceMappingURL=chunk-CDLWYZVQ.js.map