import { AiPendingActionRepository } from "../data/repositories/AiPendingActionRepository.js";
import {
  emitAiAssistantEvent
} from "../events.js";
import { AiPendingActionStateError } from "../lib/pending-action-types.js";
const metadata = {
  queue: "ai-pending-action-cleanup",
  id: "ai_assistant:cleanup-expired-pending-actions",
  concurrency: 1
};
const DEFAULT_PAGE_SIZE = 100;
const MAX_PAGES_PER_TENANT = 50;
const defaultEmitter = async (eventId, payload) => {
  await emitAiAssistantEvent(eventId, payload, {
    persistent: true
  });
};
async function discoverTenantsDefault(em, now) {
  const connection = em.getConnection();
  const rows = await connection.execute(
    `select distinct "tenant_id", "organization_id"
       from "ai_pending_actions"
      where "status" = 'pending'
        and "expires_at" < ?`,
    [now],
    "all"
  );
  if (!Array.isArray(rows)) return [];
  const out = [];
  for (const row of rows) {
    const tenantId = typeof row.tenant_id === "string" ? row.tenant_id : null;
    if (!tenantId) continue;
    const organizationId = typeof row.organization_id === "string" ? row.organization_id : null;
    out.push({ tenantId, organizationId });
  }
  return out;
}
function toIso(value, fallback) {
  if (value instanceof Date) return value.toISOString();
  if (typeof value === "string") {
    const parsed = new Date(value);
    if (!Number.isNaN(parsed.getTime())) return parsed.toISOString();
  }
  return fallback.toISOString();
}
function buildExpiredPayload(row, scope, clock) {
  const resolvedAtIso = toIso(row.resolvedAt ?? clock, clock);
  const expiresAtIso = toIso(row.expiresAt ?? clock, clock);
  return {
    pendingActionId: row.id,
    agentId: row.agentId,
    toolName: row.toolName,
    status: row.status,
    tenantId: scope.tenantId,
    organizationId: scope.organizationId ?? null,
    userId: row.createdByUserId ?? null,
    resolvedByUserId: null,
    resolvedAt: resolvedAtIso,
    expiresAt: expiresAtIso,
    expiredAt: resolvedAtIso
  };
}
async function runPendingActionCleanup(options) {
  const clock = options.now ?? /* @__PURE__ */ new Date();
  const repo = options.repo ?? new AiPendingActionRepository(options.em);
  const emitter = options.emitEvent ?? defaultEmitter;
  const pageSize = Math.max(1, Math.min(500, options.pageSize ?? DEFAULT_PAGE_SIZE));
  const discoverTenants = options.discoverTenants ?? discoverTenantsDefault;
  const summary = {
    tenantsScanned: 0,
    rowsProcessed: 0,
    rowsExpired: 0,
    rowsSkipped: 0,
    rowsErrored: 0
  };
  let tenants = [];
  try {
    tenants = await discoverTenants(options.em, clock);
  } catch (error) {
    console.error(
      "[ai-pending-action-cleanup] Failed to discover tenants:",
      error
    );
    return summary;
  }
  if (!tenants.length) return summary;
  for (const scope of tenants) {
    summary.tenantsScanned += 1;
    for (let page = 0; page < MAX_PAGES_PER_TENANT; page += 1) {
      let expiredRows;
      try {
        expiredRows = await repo.listExpired(
          {
            tenantId: scope.tenantId,
            organizationId: scope.organizationId,
            userId: null
          },
          clock,
          pageSize
        );
      } catch (error) {
        console.error(
          `[ai-pending-action-cleanup] listExpired failed for tenant ${scope.tenantId}:`,
          error
        );
        break;
      }
      if (!expiredRows.length) break;
      for (const row of expiredRows) {
        summary.rowsProcessed += 1;
        try {
          const updated = await repo.setStatus(
            row.id,
            "expired",
            {
              tenantId: scope.tenantId,
              organizationId: scope.organizationId,
              userId: null
            },
            { resolvedByUserId: null, now: clock }
          );
          summary.rowsExpired += 1;
          const payload = buildExpiredPayload(updated, scope, clock);
          try {
            await emitter("ai.action.expired", payload);
          } catch (emitError) {
            console.warn(
              `[ai-pending-action-cleanup] Failed to emit ai.action.expired for ${row.id}:`,
              emitError
            );
          }
        } catch (error) {
          if (error instanceof AiPendingActionStateError) {
            summary.rowsSkipped += 1;
            console.info(
              `[ai-pending-action-cleanup] Skipping ${row.id}: concurrent transition ${error.from} \u2192 ${error.to} already occurred`
            );
            continue;
          }
          summary.rowsErrored += 1;
          console.error(
            `[ai-pending-action-cleanup] Failed to expire ${row.id}:`,
            error
          );
        }
      }
      if (expiredRows.length < pageSize) break;
    }
  }
  return summary;
}
async function handle(_job, ctx) {
  const em = ctx.resolve("em");
  await runPendingActionCleanup({ em });
}
export {
  handle as default,
  metadata,
  runPendingActionCleanup
};
//# sourceMappingURL=ai-pending-action-cleanup.js.map
